/* HackerPort driver for Linux
 * version 0.7.2 date 2007-05-04
 * part of the hacker port project - http[s]://stefan.schuermans.info/hackerport/
 * Copyright (C) 2003-2007 Stefan Schuermans <stefan@schuermans.info>
 * Copyleft: GNU public license - http://www.gnu.org/copyleft/gpl.html
 */

//*** includes

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/bitops.h>
#include <asm/atomic.h>

#include "hkp_drv.h"



//*** debug configuration

//#define DEBUG

//#define DEBUG_MEM
#define DEBUG_USB
#define DEBUG_ACT

#ifndef DEBUG
#undef DEBUG_MEM
#undef DEBUG_USB
#undef DEBUG_ACT
#endif



//*** macros

//number of elements in an array
#define count( array ) (sizeof( (array) ) / sizeof( (array)[0] ))



//*** constants

//version information
#define hkp_drv_name "HackerPort driver for Linux"
#define hkp_drv_ver "version 0.7.2 date 2007-05-04"

//error new_ver: UPDATE numerical version information!!!
#define hkp_drv_ver_major 0
#define hkp_drv_ver_minor 7
#define hkp_drv_ver_revision 2
#define hkp_drv_ver_year 2007
#define hkp_drv_ver_month 5
#define hkp_drv_ver_day 4

//global constants concerning the device
#define hkp_drv_id_vendor 0x6666 //vendor id: prototype device
#define hkp_drv_id_product 0x484B //product id: 'HK'
//global constants concerning the module
#define hkp_drv_minor_base 0xB0 //base of minor device number
#define hkp_drv_minor_range 0x10 //range of minor device numbers

//timeout for USB operations
#define hkp_drv_timeout (HZ/20) //0.05 seconds

//time to wait after an USB bulk input error
#define hkp_drv_bulk_in_err_wait (HZ/2) //0.5 seconds



//*** debug macros

//info macros
#define hkp_drv_info( text ) printk( KERN_INFO "hkp_drv: "text"\n" )
#define hkp_drv_infof( format, arg... ) printk( KERN_INFO "hkp_drv: "format"\n", ##arg )
//warning macros
#define hkp_drv_warning( text ) printk( KERN_WARNING "hkp_drv: "text"\n" )
#define hkp_drv_warningf( format, arg... ) printk( KERN_WARNING "hkp_drv: "format"\n", ##arg )
//error macros
#define hkp_drv_error( text ) printk( KERN_ERR "hkp_drv: "text"\n" )
#define hkp_drv_errorf( format, arg... ) printk( KERN_ERR "hkp_drv: "format"\n", ##arg )
//debug macros
#ifdef DEBUG
#define hkp_drv_debug( text ) printk( KERN_DEBUG "hkp_drv: "text"\n" )
#define hkp_drv_debugf( format, arg... ) printk( KERN_DEBUG "hkp_drv: "format"\n", ##arg )
#else
#define hkp_drv_debug( text )
#define hkp_drv_debugf( format, arg... )
#endif



//*** debug functions

#ifdef DEBUG_MEM
//memory debug
struct semaphore hkp_drv_my_alloc_lock;
int hkp_drv_my_alloc_cnt = 0;
void * hkp_drv_my_kmalloc( int len, int flags )
{
  void * ptr;
  down( &hkp_drv_my_alloc_lock );
  ptr = kmalloc( len, flags );
  if( ptr )
  {
    hkp_drv_my_alloc_cnt++;
    printk( KERN_INFO "ptr=%p alloc_cnt=%d\n", ptr, hkp_drv_my_alloc_cnt );
  }
  up( &hkp_drv_my_alloc_lock );
  return ptr;
}
void hkp_drv_my_kfree( void * ptr )
{
  down( &hkp_drv_my_alloc_lock );
  kfree( ptr );
  hkp_drv_my_alloc_cnt--;
  printk( KERN_INFO "ptr=%p alloc_cnt=%d\n", ptr, hkp_drv_my_alloc_cnt );
  up( &hkp_drv_my_alloc_lock );
}
#define kmalloc hkp_drv_my_kmalloc
#define kfree hkp_drv_my_kfree
#endif



//*** wait for event with timeout
//usage:
//1) initialize a struct t_hkp_drv_wet: "hkp_drv_wet_init( &wet );"
//2) pass pointer to some source: "p_some_source->p_wet = &wet;"
//3) source will _perhaps_ generate an event: "hkp_drv_wet_event( &wet );"
//4) wait for event: "ret_val = hkp_drv_wet_wait( &wet, timeout );"
//5) source will _perhaps_ generate an event: "hkp_drv_wet_event( &wet );"
//6) check return value for timeout "if( ret_val == -ETIMEDOUT ) ..."

//the wet structure to wait for an event with timeout
typedef struct t_hkp_drv_wet
{
  unsigned long event_or_timeout; //bit 0 set on event or timeout
  int timeout; //set on timeout
  struct timer_list timeout_timer; //the timer to generate the timeout
  struct semaphore sem; //semaphore to wait on
} st_hkp_drv_wet;

//wet timeout callback function
static void hkp_drv_wet_timeout( unsigned long ul_p_wet )
{
  struct t_hkp_drv_wet * p_wet = (struct t_hkp_drv_wet *)ul_p_wet;
  //set event or timeout flag and exit if it was already set
  if( test_and_set_bit( 0, &p_wet->event_or_timeout ) )
    return;
  //set timeout indicator
  p_wet->timeout = 1;
  //now unlock the semaphore to end wait
  up( &p_wet->sem );
}

//initialize the wet sturcture
void hkp_drv_wet_init( struct t_hkp_drv_wet * p_wet )
{
  //no event or timeout yet
  p_wet->event_or_timeout = 0;
  p_wet->timeout = 0;
  //initialize timer
  init_timer( &p_wet->timeout_timer );
  p_wet->timeout_timer.data = (unsigned long)p_wet;
  p_wet->timeout_timer.function = hkp_drv_wet_timeout;
  //initialize semaphore in locked state
  init_MUTEX_LOCKED( &p_wet->sem );
}

//signal an wet event
void hkp_drv_wet_event( struct t_hkp_drv_wet * p_wet )
{
  //set event or timeout flag and exit if it was already set
  if( test_and_set_bit( 0, &p_wet->event_or_timeout ) )
    return;
  //reset timeout indicator
  p_wet->timeout = 0;
  //now unlock the semaphore to end wait
  up( &p_wet->sem );
}

//wait for wet event with timeout
//returns 0 on success, -ETIMEDOUT on timeout
int hkp_drv_wet_wait( struct t_hkp_drv_wet * p_wet, int timeout_jiffies )
{
  //start timeout timer
  p_wet->timeout_timer.expires = jiffies + max( timeout_jiffies, 1 );
  add_timer( &p_wet->timeout_timer );
  //wait for event or timeout
  down( &p_wet->sem );
  //delete timeout timer
  //(this does nothing if timer already expired)
  del_timer_sync( &p_wet->timeout_timer );
  //return if timeout or real event
  return p_wet->timeout ? -ETIMEDOUT : 0;
}



//*** global types

//type for an output step
typedef struct t_hkp_drv_out_step
{
  struct t_hkp_drv_out_step * p_next, * p_prev; //doubly linked list (or NULL)
  int msg_len; //length of message
  unsigned char * p_msg; //pointer to message (memory is allocated in same memory block behind this structure)
  struct t_hkp_drv_wet wet; //wet event signaled on completion
} st_hkp_drv_out_step;

//type for an input step
typedef struct t_hkp_drv_in_step
{
  struct t_hkp_drv_in_step * p_next, * p_prev; //doubly linked list (or NULL)
  int msg_len; //length of message
  unsigned char * p_filter; //pointer to filter (memory is allocated in same memory block behind this structure)
  unsigned char * p_mask; //pointer to filter mask (memory is allocated in same memory block behind this structure)
  unsigned char * p_msg; //pointer to message (memory is allocated in same memory block behind this structure)
  struct t_hkp_drv_wet wet; //wet event signaled on completion
} st_hkp_drv_out_in;



//type for owned ports
typedef __u16 tt_hkp_drv_own_flags;
const tt_hkp_drv_own_flags hkp_drv_own_flags_out[2] = { 0x1, 0x2 };
const tt_hkp_drv_own_flags hkp_drv_own_flags_out_all = 0x3;
const tt_hkp_drv_own_flags hkp_drv_own_flags_in[2] = { 0x4, 0x8 };
const tt_hkp_drv_own_flags hkp_drv_own_flags_in_all = 0xC;
const tt_hkp_drv_own_flags hkp_drv_own_flags_pwm[2] = { 0x10, 0x20 };
const tt_hkp_drv_own_flags hkp_drv_own_flags_pwm_all = 0x30;
const tt_hkp_drv_own_flags hkp_drv_own_flags_analog[5] = { 0x40, 0x80, 0x100, 0x200, 0x400 };
const tt_hkp_drv_own_flags hkp_drv_own_flags_analog_all = 0x7C0;
const tt_hkp_drv_own_flags hkp_drv_own_flags_ser = 0x800;
const tt_hkp_drv_own_flags hkp_drv_own_flags_i2c = 0x1000;
const tt_hkp_drv_own_flags hkp_drv_own_flags_pic = 0x2000;
const tt_hkp_drv_own_flags hkp_drv_own_flags_jtag = 0x4000;



//type for private data of a file
typedef struct t_hkp_drv_file_priv
{
  struct t_hkp_drv_dev * p_dev; //pointer to associated device
  struct t_hkp_drv_file_priv * p_next, * p_prev; //pointer to next and previous file that have opened same device

  char * p_read_str; //pointer to allocated string to output when reading or NULL if not yet allocated
  int read_str_len; //length of string to output when reading

  tt_hkp_drv_own_flags owned_ports; //owned ports
} st_hkp_drv_file_priv;

//type for private data of a device
typedef struct t_hkp_drv_dev
{
  struct semaphore lock; //used to lock this structure

  struct usb_device * p_usb_dev; //saved parameters of usb device
  const struct usb_device_id * p_usb_dev_id;

  unsigned char dev_no_sub_minor; //subminor device number (minor relative to base)
  unsigned char dev_no_minor; //minor device number

  unsigned char bulk_out_endpoint_addr[3]; //address of the bulk out endpoints
  int bulk_out_max_packet_size[3]; //maximum size of packets for bulk out endpoints
  unsigned char * p_bulk_out_buffer[3]; //pointer to buffers for bulk out
  struct urb * p_bulk_out_urb[3]; //bulk out URBs
  int bulk_out_urb_linked[3]; //boolean flags, if bulk out URBs are submitted/linked
  int next_bulk_out_index; //next bulk out URB to use

  unsigned char bulk_in_endpoint_addr[3]; //address of the bulk in endpoints
  int bulk_in_max_packet_size[3]; //maximum size of packets for bulk in endpoints
  unsigned char * p_bulk_in_buffer[3]; //pointer to buffers for bulk in
  struct urb * p_bulk_in_urb[3]; //bulk in URBs
  int bulk_in_urb_linked[3]; //boolean flags, if bulk in URBs are submitted/linked
  int bulk_in_actual_length[3]; //length of received data in buffer, -1 if none

  long bulk_in_err; //flag set if an error occured during usb bulk input (bit 0)

  unsigned char seq_no_send, seq_no_recv; //sequence numbers for packets

  int open_cnt; //numer of times this file is opened at the moment
  struct t_hkp_drv_file_priv file_priv_list; //anchor of list with files that have opened this device

  struct t_hkp_drv_out_step out_step_list; //anchor of output step list
  struct t_hkp_drv_in_step in_step_list; //anchor of input step list
  
  tt_hkp_drv_own_flags used_ports; //used ports

  unsigned char set_id_possible; //if setting id is still possible
                                 // - only possible once after probe by first user if no other user yet

  unsigned char ser_send_buf[256]; //send buffer for serial port
  unsigned short ser_send_start, ser_send_end; //ring buffer pointers
  unsigned char ser_send_clr; //flag to clear send buffer
                              //(this cannot be done in ser_baud)
  
  unsigned short ser_recv_buf[4096]; //receive buffer for serial port
                                     //0x00..0xFF: data byte
                                     //0x100..0x1FF: error reported by device
                                     //0x200: buffer overflow
  unsigned short ser_recv_start, ser_recv_end; //ring buffer pointers
} st_hkp_drv_dev;



//*** global variables

//table with pointers to private device data of devices
//(indexed by subminor)
static struct t_hkp_drv_dev * hkp_drv_dev_table[hkp_drv_minor_range];
//lock for this table
static struct semaphore hkp_drv_dev_table_lock;

//prototype of task function (needed by declaration of task queue)
#if LINUX_VERSION_CODE < 0x20614 //kernel-version 2.6.20
static void hkp_drv_task( void * p_ignored );
#else
static void hkp_drv_task( struct work_struct * work );
#endif
//task to do some things that cannot be done in interrupt
#if LINUX_VERSION_CODE < 0x20614 //kernel-version 2.6.20
DECLARE_WORK( hkp_drv_work, hkp_drv_task, NULL );
#else
DECLARE_WORK( hkp_drv_work, hkp_drv_task );
#endif
//boolean flags for tasks to do in next hkp_drv_task, modified by atomic bitops
//(hkp_drv_task is task for things that cannot be done in interrupt)
static long hkp_drv_task_flags[hkp_drv_minor_range];
#define hkp_drv_task_flag_bulk_out_try 0 //try to send something using bulk output
#define hkp_drv_task_flag_bulk_in_proc 1 //process data received unsing bulk input

//prototypes for tables below
static int hkp_drv_probe( struct usb_interface * p_interface, const struct usb_device_id * p_usb_dev_id );
static void hkp_drv_disconnect( struct usb_interface * p_interface );

//table of supported devices
static struct usb_device_id hkp_drv_id_table [] =
{
  { USB_DEVICE( hkp_drv_id_vendor, hkp_drv_id_product ) },
  { } //end of table
};

//structure for USB data and functions
static struct usb_driver hkp_drv_usb_driver =
{
#if LINUX_VERSION_CODE < 0x2060F //kernel-version 2.6.15
  .owner =      THIS_MODULE,
#endif
  .name =       "hkp_drv",
  .probe =      hkp_drv_probe,
  .disconnect = hkp_drv_disconnect,
  .id_table =   hkp_drv_id_table,
};            

//*** bulk output interrupt functions

//callback-procedure for USB bulk output
//(interrupt context)
#if LINUX_VERSION_CODE < 0x20613 //kernel-version 2.6.19
static void hkp_drv_bulk_out_callback( struct urb * p_urb, struct pt_regs * regs )
#else
static void hkp_drv_bulk_out_callback( struct urb * p_urb )
#endif
{
  struct t_hkp_drv_dev * p_dev;
  int idx;
#ifdef DEBUG_USB
  int i, j;
  char dbg_text[256];
#endif

  //get device
  p_dev = (struct t_hkp_drv_dev *)p_urb->context;

  //get index of bulk out URB
  for( idx = 0; idx < 3; idx++ )
    if( p_urb == p_dev->p_bulk_out_urb[idx] )
      break;
  //URB not found - does not seem to be our URB - this should never happen
  if( idx >= 3 )
    return;

  //bulk out URB is no more submitted/linked
  p_dev->bulk_out_urb_linked[idx] = 0;

  //error
  if( p_urb->status != 0 )
    //print error message
    hkp_drv_errorf( "bulk output %d reported error: %d", idx, p_urb->status );
  //no error
  else
  {
    //not all data was sent
    if( p_urb->actual_length != p_urb->transfer_buffer_length )
      //print warning message
      hkp_drv_warningf( "bulk output %d returned a different length: %d (expected: %d)",
          idx, p_urb->actual_length, p_urb->transfer_buffer_length );
#ifdef DEBUG_USB
    //print bulk message with data
    dbg_text[0] = 0;
    for( i = 0, j = 0; i < p_urb->actual_length && j < sizeof( dbg_text ) - 4; i++, j += 3 )
      sprintf( dbg_text + j, "%02X ", ((unsigned char *)p_urb->transfer_buffer)[i] );
    hkp_drv_debugf( "bulk output %d done (len=%d): %s", idx, p_urb->actual_length, dbg_text );
#endif
  }

  //set flag for execution of hkp_drv_bulk_out_try for this device
  //(use test_and_x here to be smp safe)
  test_and_set_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[p_dev->dev_no_sub_minor] );
  //schedule task for things that can not be done in interrupt
  schedule_work( &hkp_drv_work );
}



//*** bulk input interrupt functions

//callback-procedure for USB bulk input
//(interrupt context)
#if LINUX_VERSION_CODE < 0x20613 //kernel-version 2.6.19
static void hkp_drv_bulk_in_callback( struct urb * p_urb, struct pt_regs * regs )
#else
static void hkp_drv_bulk_in_callback( struct urb * p_urb )
#endif
{
  struct t_hkp_drv_dev * p_dev;
  int idx;
#ifdef DEBUG_USB
  int i, j;
  char dbg_text[256];
#endif

  //get device
  p_dev = (struct t_hkp_drv_dev *)p_urb->context;

  //get index of bulk in URB
  for( idx = 0; idx < 3; idx++ )
    if( p_urb == p_dev->p_bulk_in_urb[idx] )
      break;
  //URB not found - does not seem to be our URB - this should never happen
  if( idx >= 3 )
    return;

  //bulk in URB is no more submitted/linked
  p_dev->bulk_in_urb_linked[idx] = 0;

  //error
  if( p_urb->status != 0 )
  {
    //print error message
    hkp_drv_errorf( "bulk input %d reported error: %d", idx, p_urb->status );
    //set bulk input error flag
    //(use test_and_x here to be smp safe)
    test_and_set_bit( 0, &p_dev->bulk_in_err );
  }
  //no error
  else
  {
#ifdef DEBUG_USB
    //print bulk message with data
    dbg_text[0] = 0;
    for( i = 0, j = 0; i < p_urb->actual_length && j < sizeof( dbg_text ) - 4; i++, j += 3 )
      sprintf( dbg_text + j, "%02X ", ((unsigned char *)p_urb->transfer_buffer)[i] );
    hkp_drv_debugf( "bulk input %d done (len=%d): %s", idx, p_urb->actual_length, dbg_text );
#endif

    //save length of received data
    p_dev->bulk_in_actual_length[idx] = p_urb->actual_length;
  }

  //set flag for execution of hkp_drv_bulk_in_proc for this device
  //(use test_and_x here to be smp safe)
  test_and_set_bit( hkp_drv_task_flag_bulk_in_proc, &hkp_drv_task_flags[p_dev->dev_no_sub_minor] );
  //schedule task for things that can not be done in interrupt
  schedule_work( &hkp_drv_work );
}



//*** support functions for non-interrupt-time - device locked

//allocate an output step
//(p_msg may be NULL - buffer is not initialized in this case)
//returns NULL in case of error
static struct t_hkp_drv_out_step * hkp_drv_out_step_new( int msg_len, unsigned char * p_msg )
{
  struct t_hkp_drv_out_step * p_out_step;

  //allocate memory block
  p_out_step = (struct t_hkp_drv_out_step *)kmalloc( sizeof( struct t_hkp_drv_out_step ) + msg_len, GFP_KERNEL );
  if( p_out_step == NULL )
    return NULL;
  //set internal pointers
  p_out_step->p_msg = (unsigned char *)p_out_step + sizeof( struct t_hkp_drv_out_step );

  //output step not yet in a list
  p_out_step->p_prev = NULL;
  p_out_step->p_next = NULL;

  //put message into output step
  p_out_step->msg_len = msg_len;
  if( p_msg != NULL )
    memcpy( p_out_step->p_msg, p_msg, msg_len );

  //initialize wet event
  hkp_drv_wet_init( &p_out_step->wet );

  //success
  return p_out_step;
}

//append an output step to the device's output step list
//returns 0 on success, -1 on error
//(must be called with device locked)
static int hkp_drv_out_step_append( struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_out_step * p_out_step )
{
  //invalid pointer or output step is already in a list
  if( p_out_step == NULL || p_out_step->p_prev != NULL || p_out_step->p_next != NULL )
    return -1;

  //add output step to list
  p_out_step->p_prev = p_dev->out_step_list.p_prev;
  p_out_step->p_next = &p_dev->out_step_list;
  p_out_step->p_next->p_prev = p_out_step;
  p_out_step->p_prev->p_next = p_out_step;

  //success
  return 0;
}

//remove an output step from the output step list it is in
//returns 0 on success, -1 on error
//(must be called with device locked)
static int hkp_drv_out_step_remove( struct t_hkp_drv_out_step * p_out_step )
{
  //invalid pointer or output step is not in a list
  if( p_out_step == NULL || p_out_step->p_prev == NULL || p_out_step->p_next == NULL )
    return -1;

  //remove output step from list
  p_out_step->p_next->p_prev = p_out_step->p_prev;
  p_out_step->p_prev->p_next = p_out_step->p_next;
  p_out_step->p_prev = NULL;
  p_out_step->p_next = NULL;

  //success
  return 0;
}

//free an output step
//may be called with NULL
static void hkp_drv_out_step_free( struct t_hkp_drv_out_step * p_out_step )
{
  //check pointer
  if( p_out_step == NULL )
    return;

  //free the output step memory block
  kfree( p_out_step );
}

//allocate an input step
//(p_filter and/or p_mask may be NULL - buffer is not initialized in this case)
//returns NULL in case of error
static struct t_hkp_drv_in_step * hkp_drv_in_step_new( int msg_len, unsigned char * p_filter, unsigned char * p_mask )
{
  struct t_hkp_drv_in_step * p_in_step;

  //allocate memory block
  p_in_step = (struct t_hkp_drv_in_step *)kmalloc( sizeof( struct t_hkp_drv_in_step ) + 3 * msg_len, GFP_KERNEL );
  if( p_in_step == NULL )
    return NULL;
  //set internal pointers
  p_in_step->p_filter = (unsigned char *)p_in_step + sizeof( struct t_hkp_drv_in_step );
  p_in_step->p_mask = p_in_step->p_filter + msg_len;
  p_in_step->p_msg = p_in_step->p_mask + msg_len;

  //input step not yet in a list
  p_in_step->p_prev = NULL;
  p_in_step->p_next = NULL;

  //put filter and mask into input step
  p_in_step->msg_len = msg_len;
  if( p_filter != NULL )
    memcpy( p_in_step->p_filter, p_filter, msg_len );
  if( p_mask != NULL )
    memcpy( p_in_step->p_mask, p_mask, msg_len );

  //initialize wet event
  hkp_drv_wet_init( &p_in_step->wet );

  //success
  return p_in_step;
}

//append an input step to the device's input step list
//returns 0 on success, -1 on error
//(must be called with device locked)
static int hkp_drv_in_step_append( struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_in_step * p_in_step )
{
  //invalid pointer or input step is already in a list
  if( p_in_step == NULL || p_in_step->p_prev != NULL || p_in_step->p_next != NULL )
    return -1;

  //add input step to list
  p_in_step->p_prev = p_dev->in_step_list.p_prev;
  p_in_step->p_next = &p_dev->in_step_list;
  p_in_step->p_next->p_prev = p_in_step;
  p_in_step->p_prev->p_next = p_in_step;

  //success
  return 0;
}

//remove an input step from the input step list it is in
//returns 0 on success, -1 on error
//(must be called with device locked)
static int hkp_drv_in_step_remove( struct t_hkp_drv_in_step * p_in_step )
{
  //invalid pointer or input step is not in a list
  if( p_in_step == NULL || p_in_step->p_prev == NULL || p_in_step->p_next == NULL )
    return -1;

  //remove input step from list
  p_in_step->p_next->p_prev = p_in_step->p_prev;
  p_in_step->p_prev->p_next = p_in_step->p_next;
  p_in_step->p_prev = NULL;
  p_in_step->p_next = NULL;

  //success
  return 0;
}

//free an input step
//may be called with NULL
static void hkp_drv_in_step_free( struct t_hkp_drv_in_step * p_in_step )
{
  //check pointer
  if( p_in_step == NULL )
    return;

  //free the input step memory block
  kfree( p_in_step );
}

//free a device and everything that belongs to it
//device is also removed from the device table
//(the device table must be locked)
static void hkp_drv_dev_free( struct t_hkp_drv_dev * p_dev )
{
  int i;
  struct t_hkp_drv_out_step * p_out_step;
  struct t_hkp_drv_in_step * p_in_step;

  //remove device from the device table
  hkp_drv_dev_table[p_dev->dev_no_sub_minor] = NULL;

  //free URBs
  for( i = 0; i < 3; i++ )
  {
    if( p_dev->p_bulk_out_urb[i] != NULL )
    {
      if( p_dev->bulk_out_urb_linked[i] )
        usb_unlink_urb( p_dev->p_bulk_out_urb[i] );
      usb_free_urb( p_dev->p_bulk_out_urb[i] );
    }
    if( p_dev->p_bulk_in_urb[i] != NULL )
    {
      if( p_dev->bulk_in_urb_linked[i] )
        usb_unlink_urb( p_dev->p_bulk_in_urb[i] );
      usb_free_urb( p_dev->p_bulk_in_urb[i] );
    }
  }
  //free buffers
  for( i = 0; i < 3; i++ )
  {
    if( p_dev->p_bulk_out_buffer[i] != NULL )
      kfree( p_dev->p_bulk_out_buffer[i] );
    if( p_dev->p_bulk_in_buffer[i] != NULL )
      kfree( p_dev->p_bulk_in_buffer[i] );
  }
  
  //free output steps
  while( p_dev->out_step_list.p_next != &p_dev->out_step_list )
  {
    //remove first output step from list
    p_out_step = p_dev->out_step_list.p_next;
    hkp_drv_out_step_remove( p_out_step );
    //free output step
    hkp_drv_out_step_free( p_out_step );
  }

  //free input steps
  while( p_dev->in_step_list.p_next != &p_dev->in_step_list )
  {
    //remove first input step from list
    p_in_step = p_dev->in_step_list.p_next;
    hkp_drv_in_step_remove( p_in_step );
    //free input step
    hkp_drv_in_step_free( p_in_step );
  }

  //free device structure
  kfree( p_dev );
}



//*** functions to do some things that cannot be done in interrupt

//try to send something using bulk output
//(must be called with device locked)
static void hkp_drv_bulk_out_try( struct t_hkp_drv_dev * p_dev )
{
  int idx, pos;
  struct t_hkp_drv_out_step * p_out_step;
  unsigned short new_start;
#ifdef DEBUG
  int i, j;
  char dbg_text[256];
#endif

  //clear send buffer of serial port if requested
  if( p_dev->ser_send_clr )
  {
    p_dev->ser_send_start = p_dev->ser_send_end;
    p_dev->ser_send_clr = 0; //reset flag
  }

  //check if something to send is available
  if( p_dev->out_step_list.p_next == &p_dev->out_step_list && //no output step to send
      p_dev->ser_send_start == p_dev->ser_send_end ) //output buffer of serial port is empty
    //nothing to be sent - do nothing
    return;

  //get index of bulk out URB to use for next packet
  idx = p_dev->next_bulk_out_index;

  //check if bulk output URB is linked
  if( p_dev->bulk_out_urb_linked[idx] )
    //URB is still in use - do nothing
    return;

  //assemble packet
  p_dev->p_bulk_out_buffer[idx][0] = p_dev->seq_no_send++; //put sequence number into packet
  for( pos = 1; ; ) //as long as output steps and buffer space is available
  {
    //get next output step
    p_out_step = p_dev->out_step_list.p_next;
    if( p_out_step == &p_dev->out_step_list ) //no more output steps available
      break;
    //check space in buffer
    if( pos + p_out_step->msg_len >= p_dev->bulk_out_max_packet_size[idx] ) //not enough space in buffer
      break;
    //copy message into buffer
    memcpy( p_dev->p_bulk_out_buffer[idx] + pos, p_out_step->p_msg, p_out_step->msg_len );
    //advance position in buffer
    pos += p_out_step->msg_len;
    //remove message from output step list
    hkp_drv_out_step_remove( p_out_step );
    //signal wet event
    hkp_drv_wet_event( &p_out_step->wet );
  }

  //add bytes to send on serial port
  while( p_dev->ser_send_start != p_dev->ser_send_end && //byte to send availabale
         pos + 2 < p_dev->bulk_out_max_packet_size[idx] ) //enough space in buffer
  {
    new_start = p_dev->ser_send_start; //get start pointer
    p_dev->p_bulk_out_buffer[idx][pos++] = 0x21; //send a byte on serial port
    p_dev->p_bulk_out_buffer[idx][pos++] = p_dev->ser_send_buf[new_start]; //the data byte
    new_start++; //advance new start pointer
    if( new_start >= count( p_dev->ser_send_buf ) )
      new_start = 0;
    p_dev->ser_send_start = new_start; //atomically update start pointer
  }

  //set up data in bulk out URB
  usb_fill_bulk_urb( p_dev->p_bulk_out_urb[idx], p_dev->p_usb_dev, usb_sndbulkpipe( p_dev->p_usb_dev, p_dev->bulk_out_endpoint_addr[idx] ),
    p_dev->p_bulk_out_buffer[idx], pos, hkp_drv_bulk_out_callback, p_dev );

  //bulk out URB will be linked/submitted soon
  //(do this here because callback could perhaps be called from usb_submit_urb)
  p_dev->bulk_out_urb_linked[idx] = 1;
  //submit bulk out URB
  if( usb_submit_urb( p_dev->p_bulk_out_urb[idx], GFP_KERNEL ) )
  {
    //bulk out URB is not linked/submitted
    p_dev->bulk_out_urb_linked[idx] = 0;
    //print error message
    hkp_drv_errorf( "could not submit bulk out URB %d", idx );
  }
  else
  {
#ifdef DEBUG
    //print bulk message with data
    dbg_text[0] = 0;
    for( i = 0, j = 0; i < p_dev->p_bulk_out_urb[idx]->transfer_buffer_length && j < sizeof( dbg_text ) - 4; i++, j += 3 )
      sprintf( dbg_text + j, "%02X ", ((unsigned char *)p_dev->p_bulk_out_urb[idx]->transfer_buffer)[i] );
    hkp_drv_debugf( "bulk output %d started (len=%d): %s", idx, p_dev->p_bulk_out_urb[idx]->transfer_buffer_length, dbg_text );
#endif
  }

  //next time, use next bulk output URB
  p_dev->next_bulk_out_index++;
  if( p_dev->next_bulk_out_index >= 3 )
    p_dev->next_bulk_out_index = 0;  
}

//table with lengths of input messages
unsigned char hkp_drv_in_msg_lenghts[256] = 
{
//_0  _1  _2  _3  _4  _5  _6  _7  _8  _9  _A  _B  _C  _D  _E  _F
   1,  1,  1,  0,  2,  2,  3,  0,  1,  1,  1,  0, 17, 17, 33,  0, //0x0_ 
   1,  1,  1,  2,  0,  0,  0,  0,  3,  3,  3,  3,  3, 11,  0,  0, //0x1_ 
   0,  0,  2,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x2_ 
   2,  1,  2,  2,  1,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x3_ 
   1,  2,  2,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x4_ 
   1,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x5_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x6_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x7_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x8_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0x9_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0xA_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0xB_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0xC_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0xD_ 
   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, //0xE_ 
  17,  2,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  9, //0xF_ 
};

//recieved serial data (0x00..0xFF) or serial error (0x100..0x1FF)
//(must be called with device locked)
static void hkp_drv_bulk_in_ser( struct t_hkp_drv_dev * p_dev, unsigned short value )
{
  unsigned short new_end;

  //check if enough space in receive buffer of serial port
  //(there must be at least 2 free entries)
  new_end = p_dev->ser_recv_end;
  new_end++; //advance new end pointer
  if( new_end >= count( p_dev->ser_recv_buf ) )
    new_end = 0;
  if( new_end == p_dev->ser_recv_start ) //buffer already full
    return; //ignore this value, last entry in buffer is already overflow mark (0x200)
  new_end++; //advance new end pointer
  if( new_end >= count( p_dev->ser_recv_buf ) )
    new_end = 0;
  if( new_end == p_dev->ser_recv_start ) //only 1 free entry left in buffer
    value = 0x200; //put overflow mark (0x200) into buffer (instead of value)

  //put value into buffer
  new_end = p_dev->ser_recv_end; //get end pointer
  p_dev->ser_recv_buf[new_end] = value; //put value into buffer
  new_end++; //advance new end pointer
  if( new_end >= count( p_dev->ser_recv_buf ) )
    new_end = 0;
  p_dev->ser_recv_end = new_end; //atomically update end pointer
}

//process a packet received unsing bulk input
//(must be called with device locked)
static void hkp_drv_bulk_in_packet( struct t_hkp_drv_dev * p_dev, unsigned char * p_packet, int packet_len )
{
  int i, len, j;
  struct t_hkp_drv_in_step * p_in_step;

  //process all messages in this packet
  for( i = 0; i < packet_len; i += len )
  {
    //get length of message
    len = hkp_drv_in_msg_lenghts[p_packet[i]];
    if( len == 0 || i + len > packet_len ) //invalid message or truncated message
      break; //discard rest of packet
    
    //special messages
    if( p_packet[i] == 0x22 ) //serial data
    {
      hkp_drv_bulk_in_ser( p_dev, (unsigned short)p_packet[i+1] );
      continue;
    }
    if( p_packet[i] == 0x23 ) //serial error
    {
      hkp_drv_bulk_in_ser( p_dev, 0x100 | (unsigned short)p_packet[i+1] );
      continue;
    }
    
    //get next input step
    p_in_step = p_dev->in_step_list.p_next;
    if( p_in_step != &p_dev->in_step_list ) //an input step is available
    {
      //match message against input step
      if( len == p_in_step->msg_len ) //length matches
      {
        for( j = 0; j < len; j++ ) //check data
          if( (p_packet[i+j] & p_in_step->p_mask[j]) != p_in_step->p_filter[j] )
            break; //data does not match
        if( j >= len ) //message matches
        {
          //copy message to input step
          memcpy( p_in_step->p_msg, p_packet+i, len );
          //remove message from input step list
          hkp_drv_in_step_remove( p_in_step );
          //signal wet event
          hkp_drv_wet_event( &p_in_step->wet );
        }      
      }
    }
  } //for( i ...
}

//process data received unsing bulk input
//(must be called with device locked)
static void hkp_drv_bulk_in_proc( struct t_hkp_drv_dev * p_dev )
{
  int i, seq_no_correction, bulk_in_err;

  //jump here if new sequence number was guessed
  hkp_drv_bulk_in_proc_go:
  
  //process data in input buffers
  for( i = 0; i < 3; i++ )
  {
    //discard empty packet
    if( p_dev->bulk_in_actual_length[i] == 0 )
      p_dev->bulk_in_actual_length[i] = -1;
    //data available
    else if( p_dev->bulk_in_actual_length[i] > 0 )
    {
      //sequence number matches
      if( p_dev->p_bulk_in_buffer[i][0] == p_dev->seq_no_recv )
      {
        //process packet
        hkp_drv_bulk_in_packet( p_dev, p_dev->p_bulk_in_buffer[i] + 1, p_dev->bulk_in_actual_length[i] - 1 );
        //clear send buffer (i.e. mark its length as no data available)
        p_dev->bulk_in_actual_length[i] = -1;
        //increment sequence number
        p_dev->seq_no_recv++;
        //restart for loop to ensure that all other input buffer are checked again
        i = -1;
      }
      //sequence number "too small"
      else if( (char)(p_dev->seq_no_recv - p_dev->p_bulk_in_buffer[i][0]) > 0 )
      {
        //skip this packet
        //clear send buffer (i.e. mark its length as no data available)
        p_dev->bulk_in_actual_length[i] = -1;
      }
    }
  }

  //all input buffers filled, but no sequence number matches
  for( i = 0; i < 3; i++ )
    if( p_dev->bulk_in_actual_length[i] <= 0 //empty buffer (< 0) or empty packet (== 0)
     || p_dev->p_bulk_in_buffer[i][0] == p_dev->seq_no_recv ) //sequence number matches
      break;
  if( i >= 3 ) //buffers filled, no match
  {
    //all buffers contain a packet with a "too high" sequence number
    //  - e.g. we missed a packet
    //use "smallest" sequence number as new one
    seq_no_correction = 256;
    for( i = 0; i < 3; i++ )
      seq_no_correction = min( seq_no_correction,
                               (int)(unsigned char)(p_dev->p_bulk_in_buffer[i][0] - p_dev->seq_no_recv) );
    p_dev->seq_no_recv += (unsigned char)seq_no_correction;
    //now process packet
    goto hkp_drv_bulk_in_proc_go;
  }

  //get and reset bulk input error flag
  bulk_in_err = test_and_clear_bit( 0, &p_dev->bulk_in_err );
  //if there was an error during bulk input
  if( bulk_in_err )
  {
    //give time to others before restarting bulk input
    // - e.g. this is neccessary when device is unplugged to avoid bulk input to fail 1000 times and more
    set_current_state( TASK_INTERRUPTIBLE );
    schedule_timeout( hkp_drv_bulk_in_err_wait );
  }

  //restart the bulk inputs without data that are not running
  for( i = 0; i < 3; i++ )
  {
    //not runnning, no data
    if( ! p_dev->bulk_in_urb_linked[i] && p_dev->bulk_in_actual_length[i] < 0 )
    {
      //set up data in bulk in URB
      usb_fill_bulk_urb( p_dev->p_bulk_in_urb[i], p_dev->p_usb_dev, usb_rcvbulkpipe( p_dev->p_usb_dev, p_dev->bulk_in_endpoint_addr[i] ),
          p_dev->p_bulk_in_buffer[i], p_dev->bulk_in_max_packet_size[i], hkp_drv_bulk_in_callback,
          p_dev );
      //bulk in URB will be linked/submitted soon
      //(do this here because callback could perhaps be called from usb_submit_urb)
      p_dev->bulk_in_urb_linked[i] = 1;
      //submit bulk in URB
      if( usb_submit_urb( p_dev->p_bulk_in_urb[i], GFP_KERNEL ) )
      {
        //bulk in URB is not linked/submitted
        p_dev->bulk_in_urb_linked[i] = 0;
        //print error message
        hkp_drv_errorf( "could not submit bulk in URB %d", i );
      }
      //print info that bulk in was started
      hkp_drv_debugf( "bulk input %d started (max_len=%d)", i, p_dev->p_bulk_in_urb[i]->transfer_buffer_length );
    }
  }
}

//task to do some things that cannot be done in interrupt
#if LINUX_VERSION_CODE < 0x20614 //kernel-version 2.6.20
static void hkp_drv_task( void * p_ignored )
#else
static void hkp_drv_task( struct work_struct * work )
#endif
{
  int i;
  struct t_hkp_drv_dev * p_dev;

  //before waiting for any lock, peek at flags to see if we have got something to do
  for( i = 0; i < hkp_drv_minor_range; i++ )
    if( test_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[i] )
     || test_bit( hkp_drv_task_flag_bulk_in_proc, &hkp_drv_task_flags[i] ) )
      break;
  //nothing to do
  if( i >= hkp_drv_minor_range )
    return;

  //lock device table
  down( &hkp_drv_dev_table_lock );

  //check all devices in device table
  for( i = 0; i < hkp_drv_minor_range; i++ )
  {
    //get pointer to device
    p_dev = hkp_drv_dev_table[i];
    //device is there
    if( p_dev != NULL )
    {
      //lock device
      down( &p_dev->lock );

      //device was not unplugged
      if( p_dev->p_usb_dev != NULL )
      {
        //call functions whose flag is set and clear the flag
        if( test_and_clear_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[i] ) )
          hkp_drv_bulk_out_try( p_dev );
        if( test_and_clear_bit( hkp_drv_task_flag_bulk_in_proc, &hkp_drv_task_flags[i] ) )
          hkp_drv_bulk_in_proc( p_dev );
      }

      //unlock device
      up( &p_dev->lock );
    }
  }

  //unlock device table
  up( &hkp_drv_dev_table_lock );
}



//*** support functions for non-interrupt-time - device not locked

//send a message to the device
//returns 0 on success, a negative value in case of error
static int hkp_drv_send_msg( struct t_hkp_drv_dev * p_dev, int send_msg_len, unsigned char * p_send_msg )
{
  struct t_hkp_drv_out_step * p_out_step;
  int ret_val;

  //allocate output step
  p_out_step = hkp_drv_out_step_new( send_msg_len, p_send_msg );
  if( p_out_step == NULL )
    //error
    return -ENOMEM;

  //lock device
  down( &p_dev->lock );
  //device was unplugged
  if( p_dev->p_usb_dev == NULL )
  {
    //unlock device
    up( &p_dev->lock );
    //free output step
    hkp_drv_out_step_free( p_out_step );
    //no such device there
    return -ENODEV;
  }

  //submit output step
  if( hkp_drv_out_step_append( p_dev, p_out_step ) != 0 )
  {
    //unlock device
    up( &p_dev->lock );
    //free output step
    hkp_drv_out_step_free( p_out_step );
    //error
    return -1;
  }

  //unlock device
  up( &p_dev->lock );

  //set flag for execution of hkp_drv_bulk_out_try for this device
  //(use test_and_x here to be smp safe)
  test_and_set_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[p_dev->dev_no_sub_minor] );
  //schedule task for things that can not be done in interrupt
  schedule_work( &hkp_drv_work );

  //wait for wet event of output step
  ret_val = hkp_drv_wet_wait( &p_out_step->wet, hkp_drv_timeout );

  //re-lock device
  down( &p_dev->lock );

  //remove output step from list
  hkp_drv_out_step_remove( p_out_step );
  
  //unlock device
  up( &p_dev->lock );

  //free output step
  hkp_drv_out_step_free( p_out_step );

  //return error or success
  return ret_val;
}

//send a message to the device and receive an answer message
//returns 0 on success, a negative value in case of error
static int hkp_drv_send_msg_recv_msg( struct t_hkp_drv_dev * p_dev, int send_msg_len, unsigned char * p_send_msg,
                                      int recv_msg_len, unsigned char * p_recv_msg_filter, unsigned char * p_recv_msg_mask,
                                      unsigned char * p_recv_msg )
{
  struct t_hkp_drv_out_step * p_out_step;
  struct t_hkp_drv_in_step * p_in_step;
  int ret_val_out, ret_val_in;

  //allocate output step
  p_out_step = hkp_drv_out_step_new( send_msg_len, p_send_msg );
  if( p_out_step == NULL )
    //error
    return -ENOMEM;
  //allocate input step
  p_in_step = hkp_drv_in_step_new( recv_msg_len, p_recv_msg_filter, p_recv_msg_mask );
  if( p_in_step == NULL )
  {
    //free output step
    hkp_drv_out_step_free( p_out_step );
    //error
    return -ENOMEM;
  }

  //lock device
  down( &p_dev->lock );
  //device was unplugged
  if( p_dev->p_usb_dev == NULL )
  {
    //unlock device
    up( &p_dev->lock );
    //free output step and input step
    hkp_drv_out_step_free( p_out_step );
    hkp_drv_in_step_free( p_in_step );
    //no such device there
    return -ENODEV;
  }

  //submit output step
  if( hkp_drv_out_step_append( p_dev, p_out_step ) != 0 )
  {
    //unlock device
    up( &p_dev->lock );
    //free output step and input step
    hkp_drv_out_step_free( p_out_step );
    hkp_drv_in_step_free( p_in_step );
    //error
    return -1;
  }

  //submit input step
  if( hkp_drv_in_step_append( p_dev, p_in_step ) != 0 )
  {
    //remove output step from list
    hkp_drv_out_step_remove( p_out_step );
    //unlock device
    up( &p_dev->lock );
    //free output step and input step
    hkp_drv_out_step_free( p_out_step );
    hkp_drv_in_step_free( p_in_step );
    //error
    return -1;
  }

  //unlock device
  up( &p_dev->lock );

  //set flag for execution of hkp_drv_bulk_out_try for this device
  //(use test_and_x here to be smp safe)
  test_and_set_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[p_dev->dev_no_sub_minor] );
  //schedule task for things that can not be done in interrupt
  schedule_work( &hkp_drv_work );

  //wait for wet events of output step and input step
  ret_val_out = hkp_drv_wet_wait( &p_out_step->wet, hkp_drv_timeout );
  ret_val_in = hkp_drv_wet_wait( &p_in_step->wet, hkp_drv_timeout );

  //copy received message to caller's buffer
  memcpy( p_recv_msg, p_in_step->p_msg, recv_msg_len );

  //re-lock device
  down( &p_dev->lock );

  //remove output step and input step from list
  hkp_drv_out_step_remove( p_out_step );
  hkp_drv_in_step_remove( p_in_step );
  
  //unlock device
  up( &p_dev->lock );

  //free output step and input step
  hkp_drv_out_step_free( p_out_step );
  hkp_drv_in_step_free( p_in_step );

  //return error or success
  return ret_val_out != 0 ? ret_val_out : ret_val_in;
}

//set up output and input step for an action
//(*pp_out_step and *pp_in_step are initialized to NULL on call)
//(output or inputs steps already allocated need not to be freed on error)
static int hkp_drv_act_setup( tt_hkp_drv_own_flags owned_ports, struct t_hkp_drv_act * p_act, struct t_hkp_drv_out_step * * pp_out_step, struct t_hkp_drv_in_step * * pp_in_step )
{
  int i;

  switch( p_act->type )
  {
    case hkp_drv_act_out_a:
      if( hkp_drv_own_flags_out[0] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x00", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x00;
      (*pp_out_step)->p_msg[1] = p_act->data.out.x;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_out_a: %02X", p_act->data.out.x );
#endif
      return 0;
    case hkp_drv_act_out_b:
      if( hkp_drv_own_flags_out[1] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x01", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x01;
      (*pp_out_step)->p_msg[1] = p_act->data.out.x;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_out_b: %02X", p_act->data.out.x );
#endif
      return 0;
    case hkp_drv_act_out_all:
      if( hkp_drv_own_flags_out_all & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 3, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x02", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x02;
      (*pp_out_step)->p_msg[1] = p_act->data.out_all.a;
      (*pp_out_step)->p_msg[2] = p_act->data.out_all.b;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_out_all: %02X %02X", p_act->data.out_all.a, p_act->data.out_all.b );
#endif
      return 0;
    case hkp_drv_act_in_a:
      if( hkp_drv_own_flags_in[0] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x04" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x04\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_in_a" );
#endif
      return 0;
    case hkp_drv_act_in_b:
      if( hkp_drv_own_flags_in[1] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x05" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x05\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_in_b" );
#endif
      return 0;
    case hkp_drv_act_in_all:
      if( hkp_drv_own_flags_in_all & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x06" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 3, "\x06\x00\x00", "\xFF\x00\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_in_all" );
#endif
      return 0;
    case hkp_drv_act_burst_out_a:
      if( hkp_drv_own_flags_out[0] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 17, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x08", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x08;
      for( i = 0; i < 16; i++ )
        (*pp_out_step)->p_msg[i+1] = p_act->data.burst_out.steps[i].x;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_burst_out_a: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out.steps[0].x, p_act->data.burst_out.steps[1].x,
                      p_act->data.burst_out.steps[2].x, p_act->data.burst_out.steps[3].x,
                      p_act->data.burst_out.steps[4].x, p_act->data.burst_out.steps[5].x,
                      p_act->data.burst_out.steps[6].x, p_act->data.burst_out.steps[7].x,
                      p_act->data.burst_out.steps[8].x, p_act->data.burst_out.steps[9].x,
                      p_act->data.burst_out.steps[10].x, p_act->data.burst_out.steps[11].x,
                      p_act->data.burst_out.steps[12].x, p_act->data.burst_out.steps[13].x,
                      p_act->data.burst_out.steps[14].x, p_act->data.burst_out.steps[15].x );
#endif
      return 0;
    case hkp_drv_act_burst_out_b:
      if( hkp_drv_own_flags_out[1] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 17, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x09", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x09;
      for( i = 0; i < 16; i++ )
        (*pp_out_step)->p_msg[i+1] = p_act->data.burst_out.steps[i].x;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_burst_out_b: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out.steps[0].x, p_act->data.burst_out.steps[1].x,
                      p_act->data.burst_out.steps[2].x, p_act->data.burst_out.steps[3].x,
                      p_act->data.burst_out.steps[4].x, p_act->data.burst_out.steps[5].x,
                      p_act->data.burst_out.steps[6].x, p_act->data.burst_out.steps[7].x,
                      p_act->data.burst_out.steps[8].x, p_act->data.burst_out.steps[9].x,
                      p_act->data.burst_out.steps[10].x, p_act->data.burst_out.steps[11].x,
                      p_act->data.burst_out.steps[12].x, p_act->data.burst_out.steps[13].x,
                      p_act->data.burst_out.steps[14].x, p_act->data.burst_out.steps[15].x );
#endif
      return 0;
    case hkp_drv_act_burst_out_all:
      if( hkp_drv_own_flags_out_all & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 33, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x0A", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x0A;
      for( i = 0; i < 16; i++ )
      {
        (*pp_out_step)->p_msg[(i<<1)+1] = p_act->data.burst_out_all.steps[i].a;
        (*pp_out_step)->p_msg[(i<<1)+2] = p_act->data.burst_out_all.steps[i].b;
      }
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_burst_out_all: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out_all.steps[0].a, p_act->data.burst_out_all.steps[0].b,
                      p_act->data.burst_out_all.steps[1].a, p_act->data.burst_out_all.steps[1].b,
                      p_act->data.burst_out_all.steps[2].a, p_act->data.burst_out_all.steps[2].b,
                      p_act->data.burst_out_all.steps[3].a, p_act->data.burst_out_all.steps[3].b,
                      p_act->data.burst_out_all.steps[4].a, p_act->data.burst_out_all.steps[4].b,
                      p_act->data.burst_out_all.steps[5].a, p_act->data.burst_out_all.steps[5].b,
                      p_act->data.burst_out_all.steps[6].a, p_act->data.burst_out_all.steps[6].b,
                      p_act->data.burst_out_all.steps[7].a, p_act->data.burst_out_all.steps[7].b,
                      p_act->data.burst_out_all.steps[8].a, p_act->data.burst_out_all.steps[8].b,
                      p_act->data.burst_out_all.steps[9].a, p_act->data.burst_out_all.steps[9].b,
                      p_act->data.burst_out_all.steps[10].a, p_act->data.burst_out_all.steps[10].b,
                      p_act->data.burst_out_all.steps[11].a, p_act->data.burst_out_all.steps[11].b,
                      p_act->data.burst_out_all.steps[12].a, p_act->data.burst_out_all.steps[12].b,
                      p_act->data.burst_out_all.steps[13].a, p_act->data.burst_out_all.steps[13].b,
                      p_act->data.burst_out_all.steps[14].a, p_act->data.burst_out_all.steps[14].b,
                      p_act->data.burst_out_all.steps[15].a, p_act->data.burst_out_all.steps[15].b );
#endif
      return 0;
    case hkp_drv_act_burst_out_in_a:
      if( (hkp_drv_own_flags_out[0] | hkp_drv_own_flags_in[0]) & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 17, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 17, "\x0C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
                                                  "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x0C;
      for( i = 0; i < 16; i++ )
        (*pp_out_step)->p_msg[i+1] = p_act->data.burst_out_in.steps[i].o_x;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_burst_out_in_a: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out_in.steps[0].i_x, p_act->data.burst_out_in.steps[1].i_x,
                      p_act->data.burst_out_in.steps[2].i_x, p_act->data.burst_out_in.steps[3].i_x,
                      p_act->data.burst_out_in.steps[4].i_x, p_act->data.burst_out_in.steps[5].i_x,
                      p_act->data.burst_out_in.steps[6].i_x, p_act->data.burst_out_in.steps[7].i_x,
                      p_act->data.burst_out_in.steps[8].i_x, p_act->data.burst_out_in.steps[9].i_x,
                      p_act->data.burst_out_in.steps[10].i_x, p_act->data.burst_out_in.steps[11].i_x,
                      p_act->data.burst_out_in.steps[12].i_x, p_act->data.burst_out_in.steps[13].i_x,
                      p_act->data.burst_out_in.steps[14].i_x, p_act->data.burst_out_in.steps[15].i_x );
#endif
      return 0;
    case hkp_drv_act_burst_out_in_b:
      if( (hkp_drv_own_flags_out[1] | hkp_drv_own_flags_in[1]) & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 17, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 17, "\x0D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
                                                  "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x0D;
      for( i = 0; i < 16; i++ )
        (*pp_out_step)->p_msg[i+1] = p_act->data.burst_out_in.steps[i].o_x;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_burst_out_in_b: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out_in.steps[0].o_x, p_act->data.burst_out_in.steps[1].o_x,
                      p_act->data.burst_out_in.steps[2].o_x, p_act->data.burst_out_in.steps[3].o_x,
                      p_act->data.burst_out_in.steps[4].o_x, p_act->data.burst_out_in.steps[5].o_x,
                      p_act->data.burst_out_in.steps[6].o_x, p_act->data.burst_out_in.steps[7].o_x,
                      p_act->data.burst_out_in.steps[8].o_x, p_act->data.burst_out_in.steps[9].o_x,
                      p_act->data.burst_out_in.steps[10].o_x, p_act->data.burst_out_in.steps[11].o_x,
                      p_act->data.burst_out_in.steps[12].o_x, p_act->data.burst_out_in.steps[13].o_x,
                      p_act->data.burst_out_in.steps[14].o_x, p_act->data.burst_out_in.steps[15].o_x );
#endif
      return 0;
    case hkp_drv_act_burst_out_in_all:
      if( (hkp_drv_own_flags_out_all | hkp_drv_own_flags_in_all) & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 33, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 33, "\x0E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
                                                  "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x0E;
      for( i = 0; i < 16; i++ )
      {
        (*pp_out_step)->p_msg[(i<<1)+1] = p_act->data.burst_out_in_all.steps[i].o_a;
        (*pp_out_step)->p_msg[(i<<1)+2] = p_act->data.burst_out_in_all.steps[i].o_b;
      }
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_burst_out_in_all: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out_in_all.steps[0].o_a, p_act->data.burst_out_in_all.steps[0].o_b,
                      p_act->data.burst_out_in_all.steps[1].o_a, p_act->data.burst_out_in_all.steps[1].o_b,
                      p_act->data.burst_out_in_all.steps[2].o_a, p_act->data.burst_out_in_all.steps[2].o_b,
                      p_act->data.burst_out_in_all.steps[3].o_a, p_act->data.burst_out_in_all.steps[3].o_b,
                      p_act->data.burst_out_in_all.steps[4].o_a, p_act->data.burst_out_in_all.steps[4].o_b,
                      p_act->data.burst_out_in_all.steps[5].o_a, p_act->data.burst_out_in_all.steps[5].o_b,
                      p_act->data.burst_out_in_all.steps[6].o_a, p_act->data.burst_out_in_all.steps[6].o_b,
                      p_act->data.burst_out_in_all.steps[7].o_a, p_act->data.burst_out_in_all.steps[7].o_b,
                      p_act->data.burst_out_in_all.steps[8].o_a, p_act->data.burst_out_in_all.steps[8].o_b,
                      p_act->data.burst_out_in_all.steps[9].o_a, p_act->data.burst_out_in_all.steps[9].o_b,
                      p_act->data.burst_out_in_all.steps[10].o_a, p_act->data.burst_out_in_all.steps[10].o_b,
                      p_act->data.burst_out_in_all.steps[11].o_a, p_act->data.burst_out_in_all.steps[11].o_b,
                      p_act->data.burst_out_in_all.steps[12].o_a, p_act->data.burst_out_in_all.steps[12].o_b,
                      p_act->data.burst_out_in_all.steps[13].o_a, p_act->data.burst_out_in_all.steps[13].o_b,
                      p_act->data.burst_out_in_all.steps[14].o_a, p_act->data.burst_out_in_all.steps[14].o_b,
                      p_act->data.burst_out_in_all.steps[15].o_a, p_act->data.burst_out_in_all.steps[15].o_b );
#endif
      return 0;
    case hkp_drv_act_pwm_a:
      if( hkp_drv_own_flags_pwm[0] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 3, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x10", "\xFF" )) == NULL )
        return -ENOMEM;
      p_act->data.pwm.x &= 0x03FF;
      (*pp_out_step)->p_msg[0] = 0x10;
      (*pp_out_step)->p_msg[1] = (unsigned char)((p_act->data.pwm.x & 0x03FC) >> 2);
      (*pp_out_step)->p_msg[2] = (unsigned char)((p_act->data.pwm.x & 0x0003) << 6);
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_pwm_a: %d", p_act->data.pwm.x );
#endif
      return 0;
    case hkp_drv_act_pwm_b:
      if( hkp_drv_own_flags_pwm[1] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 3, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x11", "\xFF" )) == NULL )
        return -ENOMEM;
      p_act->data.pwm.x &= 0x03FF;
      (*pp_out_step)->p_msg[0] = 0x11;
      (*pp_out_step)->p_msg[1] = (unsigned char)((p_act->data.pwm.x & 0x03FC) >> 2);
      (*pp_out_step)->p_msg[2] = (unsigned char)((p_act->data.pwm.x & 0x0003) << 6);
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_pwm_b: %d", p_act->data.pwm.x );
#endif
      return 0;
    case hkp_drv_act_pwm_all:
      if( hkp_drv_own_flags_pwm_all & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 5, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x12", "\xFF" )) == NULL )
        return -ENOMEM;
      p_act->data.pwm_all.a &= 0x03FF;
      p_act->data.pwm_all.b &= 0x03FF;
      (*pp_out_step)->p_msg[0] = 0x12;
      (*pp_out_step)->p_msg[1] = (unsigned char)((p_act->data.pwm_all.a & 0x03FC) >> 2);
      (*pp_out_step)->p_msg[2] = (unsigned char)((p_act->data.pwm_all.a & 0x0003) << 6);
      (*pp_out_step)->p_msg[3] = (unsigned char)((p_act->data.pwm_all.b & 0x03FC) >> 2);
      (*pp_out_step)->p_msg[4] = (unsigned char)((p_act->data.pwm_all.b & 0x0003) << 6);
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_pwm_all: %d %d", p_act->data.pwm_all.a, p_act->data.pwm_all.b );
#endif
      return 0;
    case hkp_drv_act_pwm_speed:
      if( hkp_drv_own_flags_pwm_all & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x13\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x13;
      (*pp_out_step)->p_msg[1] = p_act->data.pwm_speed.freq;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_pwm_speed: %d", p_act->data.pwm_speed.freq );
#endif
      return 0;
    case hkp_drv_act_analog_a:
      if( hkp_drv_own_flags_analog[0] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x18" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 3, "\x18\x00\x00", "\xFF\x00\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_analog_a" );
#endif
      return 0;
    case hkp_drv_act_analog_b:
      if( hkp_drv_own_flags_analog[1] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x19" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 3, "\x19\x00\x00", "\xFF\x00\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_analog_b" );
#endif
      return 0;
    case hkp_drv_act_analog_c:
      if( hkp_drv_own_flags_analog[2] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x1A" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 3, "\x1A\x00\x00", "\xFF\x00\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_analog_c" );
#endif
      return 0;
    case hkp_drv_act_analog_d:
      if( hkp_drv_own_flags_analog[3] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x1B" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 3, "\x1B\x00\x00", "\xFF\x00\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_analog_d" );
#endif
      return 0;
    case hkp_drv_act_analog_e:
      if( hkp_drv_own_flags_analog[4] & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x1C" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 3, "\x1C\x00\x00", "\xFF\x00\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_analog_e" );
#endif
      return 0;
    case hkp_drv_act_analog_all:
      if( hkp_drv_own_flags_analog_all & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x1D" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 11, "\x1D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_analog_all" );
#endif
      return 0;
    case hkp_drv_act_i2c_speed:
      if( hkp_drv_own_flags_i2c & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x30\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x30;
      (*pp_out_step)->p_msg[1] = p_act->data.i2c_speed.bit_time;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_i2c_speed: %d", p_act->data.i2c_speed.bit_time );
#endif
      return 0;
    case hkp_drv_act_i2c_start:
      if( hkp_drv_own_flags_i2c & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x31" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x31", "\xFF" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_i2c_start" );
#endif
      return 0;
    case hkp_drv_act_i2c_write:
      if( hkp_drv_own_flags_i2c & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x32;
      (*pp_out_step)->p_msg[1] = p_act->data.i2c_write.data;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x32\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_i2c_write: %d", p_act->data.i2c_write.data );
#endif
      return 0;
    case hkp_drv_act_i2c_read:
      if( hkp_drv_own_flags_i2c & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x033" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x33\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_i2c_read" );
#endif
      return 0;
    case hkp_drv_act_i2c_ack:
      if( hkp_drv_own_flags_i2c & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x34" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x34", "\xFF" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_i2c_ack" );
#endif
      return 0;
    case hkp_drv_act_i2c_nak:
      if( hkp_drv_own_flags_i2c & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x35" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x35", "\xFF" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_i2c_nak" );
#endif
      return 0;
    case hkp_drv_act_i2c_stop:
      if( hkp_drv_own_flags_i2c & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x36" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x36", "\xFF" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_i2c_stop" );
#endif
      return 0;
    case hkp_drv_act_pic_set:
      if( hkp_drv_own_flags_pic & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x40", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x40;
      (*pp_out_step)->p_msg[1] = 0x00;
      switch( p_act->data.pic_set.vpp )
      {
        case HKP_DRV_PIC_VPP_0V: (*pp_out_step)->p_msg[1] |= 0x01; break;
        case HKP_DRV_PIC_VPP_13V: (*pp_out_step)->p_msg[1] |= 0x02; break;
        case HKP_DRV_PIC_VPP_OPEN: (*pp_out_step)->p_msg[1] |= 0x03; break;
      }
      switch( p_act->data.pic_set.pgc )
      {
        case HKP_DRV_PIC_PGC_LOW: (*pp_out_step)->p_msg[1] |= 0x04; break;
        case HKP_DRV_PIC_PGC_HIGH: (*pp_out_step)->p_msg[1] |= 0x08; break;
      }
      switch( p_act->data.pic_set.pgd )
      {
        case HKP_DRV_PIC_PGD_LOW: (*pp_out_step)->p_msg[1] |= 0x10; break;
        case HKP_DRV_PIC_PGD_HIGH: (*pp_out_step)->p_msg[1] |= 0x20; break;
        case HKP_DRV_PIC_PGD_OPEN: (*pp_out_step)->p_msg[1] |= 0x30; break;
      }
#ifdef DEBUG_ACT
      char * vpp = "", * pgc = "", * pgd = "";
      switch( p_act->data.pic_set.vpp )
      {
        case HKP_DRV_PIC_VPP_0V: vpp = "0V"; break;
        case HKP_DRV_PIC_VPP_13V: vpp = "13V"; break;
        case HKP_DRV_PIC_VPP_OPEN: vpp = "open"; break;
      }
      switch( p_act->data.pic_set.pgc )
      {
        case HKP_DRV_PIC_PGC_LOW: pgc = "low"; break;
        case HKP_DRV_PIC_PGC_HIGH: pgc = "high"; break;
      }
      switch( p_act->data.pic_set.pgd )
      {
        case HKP_DRV_PIC_PGD_LOW: pgd = "low"; break;
        case HKP_DRV_PIC_PGD_HIGH: pgd = "high"; break;
        case HKP_DRV_PIC_PGD_OPEN: pgd = "open"; break;
      }
      hkp_drv_debugf( "set up act_pic_set: VPP=%s PGC=%s PGD=%s", vpp, pgc, pgd );
#endif
      return 0;
    case hkp_drv_act_pic_get:
      if( hkp_drv_own_flags_pic & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x41" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x41\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_pic_get" );
#endif
      return 0;
    case hkp_drv_act_pic_out:
      if( hkp_drv_own_flags_pic & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 4, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x42\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x42;
      (*pp_out_step)->p_msg[1] = p_act->data.pic_out_in.cnt > 0x10 ? 0x10 : p_act->data.pic_out_in.cnt;
      (*pp_out_step)->p_msg[2] = (unsigned char)((p_act->data.pic_out_in.data >> 8) & 0xFF);
      (*pp_out_step)->p_msg[3] = (unsigned char)(p_act->data.pic_out_in.data & 0xFF);
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_pic_out cnt=%d data=%04X", p_act->data.pic_out_in.cnt, p_act->data.pic_out_in.data );
#endif
      return 0;
    case hkp_drv_act_pic_in:
      if( hkp_drv_own_flags_pic & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 4, "\x43\x00\x00\x00", "\xFF\x00\x00\x00" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x43;
      (*pp_out_step)->p_msg[1] = p_act->data.pic_out_in.cnt > 0x10 ? 0x10 : p_act->data.pic_out_in.cnt;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "set up act_pic_in cnt=%d", p_act->data.pic_out_in.cnt );
#endif
      return 0;
    case hkp_drv_act_jtag_set:
      if( hkp_drv_own_flags_jtag & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 2, NULL )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 1, "\x50", "\xFF" )) == NULL )
        return -ENOMEM;
      (*pp_out_step)->p_msg[0] = 0x50;
      (*pp_out_step)->p_msg[1] = 0x00;
      switch( p_act->data.jtag_set.ntrst )
      {
        case HKP_DRV_JTAG_LOW: (*pp_out_step)->p_msg[1] |= 0x01; break;
        case HKP_DRV_JTAG_HIGH: (*pp_out_step)->p_msg[1] |= 0x02; break;
      }
      switch( p_act->data.jtag_set.tms )
      {
        case HKP_DRV_JTAG_LOW: (*pp_out_step)->p_msg[1] |= 0x04; break;
        case HKP_DRV_JTAG_HIGH: (*pp_out_step)->p_msg[1] |= 0x08; break;
      }
      switch( p_act->data.jtag_set.tck )
      {
        case HKP_DRV_JTAG_LOW: (*pp_out_step)->p_msg[1] |= 0x10; break;
        case HKP_DRV_JTAG_HIGH: (*pp_out_step)->p_msg[1] |= 0x20; break;
      }
      switch( p_act->data.jtag_set.tdi )
      {
        case HKP_DRV_JTAG_LOW: (*pp_out_step)->p_msg[1] |= 0x40; break;
        case HKP_DRV_JTAG_HIGH: (*pp_out_step)->p_msg[1] |= 0x80; break;
      }
#ifdef DEBUG_ACT
      char * ntrst = "", * tms = "", * tck = "", * tdi = "";
      switch( p_act->data.jtag_set.ntrst )
      {
        case HKP_DRV_JTAG_LOW: ntrst = "low"; break;
        case HKP_DRV_JTAG_HIGH: ntrst = "high"; break;
      }
      switch( p_act->data.jtag_set.tms )
      {
        case HKP_DRV_JTAG_LOW: tms = "low"; break;
        case HKP_DRV_JTAG_HIGH: tms = "high"; break;
      }
      switch( p_act->data.jtag_set.tck )
      {
        case HKP_DRV_JTAG_LOW: tck = "low"; break;
        case HKP_DRV_JTAG_HIGH: tck = "high"; break;
      }
      switch( p_act->data.jtag_set.tdi )
      {
        case HKP_DRV_JTAG_LOW: tdi = "low"; break;
        case HKP_DRV_JTAG_HIGH: tdi = "high"; break;
      }
      hkp_drv_debugf( "set up act_pic_set: nTRST=%s TMS=%s TCK=%s TDO=%s", ntrst, tms, tck, tdi );
#endif
      return 0;
    case hkp_drv_act_jtag_get:
      if( hkp_drv_own_flags_jtag & ~owned_ports )
        return -EPERM;
      if( (*pp_out_step = hkp_drv_out_step_new( 1, "\x051" )) == NULL )
        return -ENOMEM;
      if( (*pp_in_step = hkp_drv_in_step_new( 2, "\x51\x00", "\xFF\x00" )) == NULL )
        return -ENOMEM;
#ifdef DEBUG_ACT
      hkp_drv_debug( "set up act_jtag_get" );
#endif
      return 0;
    default:
      return 0;
  }
}

//get result of action
static void hkp_drv_act_result( struct t_hkp_drv_act * p_act, struct t_hkp_drv_in_step * p_in_step )
{
  int i;

  switch( p_act->type )
  {
    case hkp_drv_act_in_a:
    case hkp_drv_act_in_b:
      p_act->data.in.x = p_in_step->p_msg[1];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_in_x: %02X", p_act->data.in.x );
#endif
      break;
    case hkp_drv_act_in_all:
      p_act->data.in_all.a = p_in_step->p_msg[1];
      p_act->data.in_all.b = p_in_step->p_msg[2];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_in_all: %02X %02X", p_act->data.in_all.a, p_act->data.in_all.b );
#endif
      break;
    case hkp_drv_act_burst_out_in_a:
    case hkp_drv_act_burst_out_in_b:
      for( i = 0; i < 16; i++ )
        p_act->data.burst_out_in.steps[i].i_x = p_in_step->p_msg[i+1];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_burst_out_in_x: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out_in.steps[0].i_x, p_act->data.burst_out_in.steps[1].i_x,
                      p_act->data.burst_out_in.steps[2].i_x, p_act->data.burst_out_in.steps[3].i_x,
                      p_act->data.burst_out_in.steps[4].i_x, p_act->data.burst_out_in.steps[5].i_x,
                      p_act->data.burst_out_in.steps[6].i_x, p_act->data.burst_out_in.steps[7].i_x,
                      p_act->data.burst_out_in.steps[8].i_x, p_act->data.burst_out_in.steps[9].i_x,
                      p_act->data.burst_out_in.steps[10].i_x, p_act->data.burst_out_in.steps[11].i_x,
                      p_act->data.burst_out_in.steps[12].i_x, p_act->data.burst_out_in.steps[13].i_x,
                      p_act->data.burst_out_in.steps[14].i_x, p_act->data.burst_out_in.steps[15].i_x );
#endif
      break;
    case hkp_drv_act_burst_out_in_all:
      for( i = 0; i < 16; i++ )
      {
        p_act->data.burst_out_in_all.steps[i].i_a = p_in_step->p_msg[(i<<1)+1];
        p_act->data.burst_out_in_all.steps[i].i_b = p_in_step->p_msg[(i<<1)+2];
      }
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_burst_out_in_all: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                      p_act->data.burst_out_in_all.steps[0].i_a, p_act->data.burst_out_in_all.steps[0].i_b,
                      p_act->data.burst_out_in_all.steps[1].i_a, p_act->data.burst_out_in_all.steps[1].i_b,
                      p_act->data.burst_out_in_all.steps[2].i_a, p_act->data.burst_out_in_all.steps[2].i_b,
                      p_act->data.burst_out_in_all.steps[3].i_a, p_act->data.burst_out_in_all.steps[3].i_b,
                      p_act->data.burst_out_in_all.steps[4].i_a, p_act->data.burst_out_in_all.steps[4].i_b,
                      p_act->data.burst_out_in_all.steps[5].i_a, p_act->data.burst_out_in_all.steps[5].i_b,
                      p_act->data.burst_out_in_all.steps[6].i_a, p_act->data.burst_out_in_all.steps[6].i_b,
                      p_act->data.burst_out_in_all.steps[7].i_a, p_act->data.burst_out_in_all.steps[7].i_b,
                      p_act->data.burst_out_in_all.steps[8].i_a, p_act->data.burst_out_in_all.steps[8].i_b,
                      p_act->data.burst_out_in_all.steps[9].i_a, p_act->data.burst_out_in_all.steps[9].i_b,
                      p_act->data.burst_out_in_all.steps[10].i_a, p_act->data.burst_out_in_all.steps[10].i_b,
                      p_act->data.burst_out_in_all.steps[11].i_a, p_act->data.burst_out_in_all.steps[11].i_b,
                      p_act->data.burst_out_in_all.steps[12].i_a, p_act->data.burst_out_in_all.steps[12].i_b,
                      p_act->data.burst_out_in_all.steps[13].i_a, p_act->data.burst_out_in_all.steps[13].i_b,
                      p_act->data.burst_out_in_all.steps[14].i_a, p_act->data.burst_out_in_all.steps[14].i_b,
                      p_act->data.burst_out_in_all.steps[15].i_a, p_act->data.burst_out_in_all.steps[15].i_b );
#endif
      break;
    case hkp_drv_act_pwm_speed:
      p_act->data.pwm_speed.freq = p_in_step->p_msg[1];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_pwm_speed: %d", p_act->data.pwm_speed.freq );
#endif
      break;
    case hkp_drv_act_analog_a:
    case hkp_drv_act_analog_b:
    case hkp_drv_act_analog_c:
    case hkp_drv_act_analog_d:
    case hkp_drv_act_analog_e:
      p_act->data.analog.x = (unsigned short)p_in_step->p_msg[1] << 2 | (unsigned short)p_in_step->p_msg[2] >> 6;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_analog_x: %d", p_act->data.analog.x );
#endif
      break;
    case hkp_drv_act_analog_all:
      p_act->data.analog_all.a = (unsigned short)p_in_step->p_msg[1] << 2 | (unsigned short)p_in_step->p_msg[2] >> 6;
      p_act->data.analog_all.b = (unsigned short)p_in_step->p_msg[3] << 2 | (unsigned short)p_in_step->p_msg[4] >> 6;
      p_act->data.analog_all.c = (unsigned short)p_in_step->p_msg[5] << 2 | (unsigned short)p_in_step->p_msg[6] >> 6;
      p_act->data.analog_all.d = (unsigned short)p_in_step->p_msg[7] << 2 | (unsigned short)p_in_step->p_msg[8] >> 6;
      p_act->data.analog_all.e = (unsigned short)p_in_step->p_msg[9] << 2 | (unsigned short)p_in_step->p_msg[10] >> 6;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_analog_all: %d %d %d %d %d",
                      p_act->data.analog_all.a,
                      p_act->data.analog_all.b,
                      p_act->data.analog_all.c,
                      p_act->data.analog_all.d,
                      p_act->data.analog_all.e );
#endif
      break;
    case hkp_drv_act_out_a:
    case hkp_drv_act_out_b:
    case hkp_drv_act_out_all:
    case hkp_drv_act_pwm_a:
    case hkp_drv_act_pwm_b:
    case hkp_drv_act_pwm_all:
    case hkp_drv_act_burst_out_a:
    case hkp_drv_act_burst_out_b:
    case hkp_drv_act_burst_out_all:
#ifdef DEBUG_ACT
      hkp_drv_debug( "result of act_out / act_pwm_a/b/all / act_burst_out: -" );
#endif
      break;
    case hkp_drv_act_i2c_speed:
      p_act->data.i2c_speed.bit_time = p_in_step->p_msg[1];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_i2c_speed: %d", p_act->data.i2c_speed.bit_time );
#endif
      break;
    case hkp_drv_act_i2c_write:
      p_act->data.i2c_write.acked = p_in_step->p_msg[1] == 0 ? 0 : 1;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_i2c_write: %d", p_act->data.i2c_write.acked );
#endif
      break;
    case hkp_drv_act_i2c_read:
      p_act->data.i2c_read.data = (unsigned char)p_in_step->p_msg[1];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_i2c_read: %02X", p_act->data.i2c_read.data );
#endif
      break;
    case hkp_drv_act_i2c_start:
    case hkp_drv_act_i2c_ack:
    case hkp_drv_act_i2c_nak:
    case hkp_drv_act_i2c_stop:
#ifdef DEBUG_ACT
      hkp_drv_debug( "result of act_i2c_start/ack/nak/stop: -" );
#endif
      break;
    case hkp_drv_act_pic_set:
#ifdef DEBUG_ACT
      hkp_drv_debug( "result of act_pic_set: -" );
#endif
      break;
    case hkp_drv_act_pic_get:
      p_act->data.pic_get.pgd = p_in_step->p_msg[1] != 0x00 ? HKP_DRV_PIC_PGD_HIGH : HKP_DRV_PIC_PGD_LOW;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_pic_get: PGD=%s", p_act->data.pic_get.pgd == HKP_DRV_PIC_PGD_HIGH ? "high" : "low" );
#endif
      break;
    case hkp_drv_act_pic_out:
      p_act->data.pic_out_in.cnt = (unsigned char)p_in_step->p_msg[1];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_pic_out: cnt=%d", p_act->data.pic_out_in.cnt );
#endif
      break;
    case hkp_drv_act_pic_in:
      p_act->data.pic_out_in.cnt = (unsigned char)p_in_step->p_msg[1];
      p_act->data.pic_out_in.data = (unsigned short)p_in_step->p_msg[2] << 8 | (unsigned short)p_in_step->p_msg[3];
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_pic_in: cnt=%d data=%04X", p_act->data.pic_out_in.cnt, p_act->data.pic_out_in.data );
#endif
      break;
    case hkp_drv_act_jtag_set:
#ifdef DEBUG_ACT
      hkp_drv_debug( "result of act_jtag_set: -" );
#endif
      break;
    case hkp_drv_act_jtag_get:
      p_act->data.jtag_get.tdo = p_in_step->p_msg[1] != 0x00 ? HKP_DRV_JTAG_HIGH : HKP_DRV_JTAG_LOW;
#ifdef DEBUG_ACT
      hkp_drv_debugf( "result of act_jtag_get: TDO=%s", p_act->data.jtag_get.tdo == HKP_DRV_JTAG_HIGH ? "high" : "low" );
#endif
      break;
  }
}

//execute actions
// - allocate output steps and input steps
// - fill in data
// - submit input and output steps
// - wait for completion
// - get result data
// - free output and input steps
//returns 0 on success, a negative value in case of error
static int hkp_drv_acts_exec( struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, int act_cnt, struct t_hkp_drv_act * p_acts )
{
  struct t_hkp_drv_out_step * * pp_out_steps;
  struct t_hkp_drv_in_step * * pp_in_steps;
  int ret_val, i;

  //allocate arrays for output and input step pointers
  pp_out_steps = (struct t_hkp_drv_out_step * *)kmalloc( act_cnt * sizeof( struct t_hkp_drv_out_step * ), GFP_KERNEL );
  if( pp_out_steps == NULL )
    return -ENOMEM;
  pp_in_steps = (struct t_hkp_drv_in_step * *)kmalloc( act_cnt * sizeof( struct t_hkp_drv_in_step * ), GFP_KERNEL );
  if( pp_in_steps == NULL )
  {
    kfree( pp_out_steps );
    return -ENOMEM;
  }

  //no output or input step allocated yet
  for( i = 0; i < act_cnt; i++ )
  {
    pp_out_steps[i] = NULL;
    pp_in_steps[i] = NULL;
  }

  //lock device
  down( &p_dev->lock );
  //device was unplugged
  if( p_dev->p_usb_dev == NULL )
  {
    //cleanup and return error: no such device there
    ret_val = -ENODEV;
    goto hkp_drv_acts_exec_err;
  }

  //set up output and input steps for actions
  //(device needs to be locked already for this because we must ensure our port ownership is not changed)
  for( i = 0; i < act_cnt; i++ )
  {
    ret_val = hkp_drv_act_setup( p_file_priv->owned_ports, &p_acts[i], &pp_out_steps[i], &pp_in_steps[i] );
    if( ret_val != 0 )
      goto hkp_drv_acts_exec_err;
  }

  //submit output and input steps to lists
  for( i = 0; i < act_cnt; i++ )
  {
    if( (pp_out_steps[i] != NULL && hkp_drv_out_step_append( p_dev, pp_out_steps[i] ) != 0)
     || (pp_in_steps[i] != NULL && hkp_drv_in_step_append( p_dev, pp_in_steps[i] ) != 0) )
    {
      //cleanup and return error
      ret_val = -1;
      goto hkp_drv_acts_exec_err;
    }
  }

  //unlock device
  up( &p_dev->lock );

  //set flag for execution of hkp_drv_bulk_out_try for this device
  //(use test_and_x here to be smp safe)
  test_and_set_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[p_dev->dev_no_sub_minor] );
  //schedule task for things that can not be done in interrupt
  schedule_work( &hkp_drv_work );

  //wait for wet events of output steps and input steps
  //  - first wait for all output steps for not forcing strict serial behaviour
  ret_val = 0; //no error yet
  for( i = 0; i < act_cnt; i++ )
  {
    if( pp_out_steps[i] != NULL )
      p_acts[i].err_out = -hkp_drv_wet_wait( &pp_out_steps[i]->wet, hkp_drv_timeout );
    else
      p_acts[i].err_out = 0;
    if( p_acts[i].err_out != 0 && ret_val == 0 ) //remember first error
      ret_val = -p_acts[i].err_out;
  }
  for( i = 0; i < act_cnt; i++ )
  {
    if( pp_in_steps[i] != NULL )
      p_acts[i].err_in = -hkp_drv_wet_wait( &pp_in_steps[i]->wet, hkp_drv_timeout );
    else
      p_acts[i].err_in = 0;
    if( p_acts[i].err_in != 0 && ret_val == 0 ) //remember first error
      ret_val = -p_acts[i].err_in;
  }

  //get results of actions
  for( i = 0; i < act_cnt; i++ )
    hkp_drv_act_result( &p_acts[i], pp_in_steps[i] );

  //re-lock device
  down( &p_dev->lock );

  //jump here on error to clean up
  //device must be locked when jumping here)
  //(error code must be in ret_val)
  hkp_drv_acts_exec_err:

  //remove output and input steps from list
  for( i = 0; i < act_cnt; i++ )
  {
    hkp_drv_out_step_remove( pp_out_steps[i] );
    hkp_drv_in_step_remove( pp_in_steps[i] );
  }

  //unlock device
  up( &p_dev->lock );

  //free output and input steps
  for( i = 0; i < act_cnt; i++ )
  {
    hkp_drv_out_step_free( pp_out_steps[i] );
    hkp_drv_in_step_free( pp_in_steps[i] );
  }
  //free arrays for output and input step pointers
  kfree( pp_out_steps );
  kfree( pp_in_steps );

  //return error code
  return ret_val;
}



//*** file operations

//an userspace process is reading from one of the devices
static ssize_t hkp_drv_file_read( struct file * p_file, char * p_buffer, size_t count, loff_t * p_pos )
{
  struct t_hkp_drv_file_priv * p_file_priv;
  struct t_hkp_drv_dev * p_dev;
  int cnt;

  //test pointer
  if( !access_ok( VERIFY_WRITE, p_buffer, count ) )
    //pointer not okay
    return -EFAULT;

  //get private file
  p_file_priv = (struct t_hkp_drv_file_priv *)p_file->private_data;
  //get private device
  p_dev = p_file_priv->p_dev;
  //no pointer to device
  if( p_dev == NULL )
    //so there is no device
    return -ENODEV;

  //nothing to read for now
  cnt = 0;

  //debug message
  hkp_drv_debugf( "read from file %p (%d, %d): %d bytes", p_file, USB_MAJOR, p_dev->dev_no_minor, cnt );

  //return number of bytes read
  return cnt;
}

//an userspace process is writing to one of the devices
static ssize_t hkp_drv_file_write( struct file * p_file, const char * p_buffer, size_t count, loff_t * p_pos )
{
  struct t_hkp_drv_file_priv * p_file_priv;
  struct t_hkp_drv_dev * p_dev;
  int cnt;

  //test pointer
  if( !access_ok( VERIFY_READ, p_buffer, count ) )
    //pointer not okay
    return -EFAULT;

  //get private file
  p_file_priv = (struct t_hkp_drv_file_priv *)p_file->private_data;
  //get private device
  p_dev = p_file_priv->p_dev;
  //no pointer to device
  if( p_dev == NULL )
    //so there is no device
    return -ENODEV;

  //just ignore written data for now
  cnt = (int)count;

  //debug message
  hkp_drv_debugf( "wrote to file %p (%d, %d): %d bytes", p_file, USB_MAJOR, p_dev->dev_no_minor, cnt );

  //return number of bytes written to this device
  return cnt;
}

//get version of driver
static int hkp_drv_file_ioctl_ver( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_ver * up_ver )
{
  struct t_hkp_drv_ver ver;

  //test pointer
  if( !access_ok( VERIFY_WRITE, up_ver, sizeof( struct t_hkp_drv_ver ) ) )
    //pointer not okay
    return -EFAULT;

  //fill local structure
  ver.major = hkp_drv_ver_major;
  ver.minor = hkp_drv_ver_minor;
  ver.revision = hkp_drv_ver_revision;
  ver.year = hkp_drv_ver_year;
  ver.month = hkp_drv_ver_month;
  ver.day = hkp_drv_ver_day;

  //copy structure to user-space
  copy_to_user( up_ver, &ver, sizeof( struct t_hkp_drv_ver ) );

  //debug message
  hkp_drv_debugf( "ioctl \"ver\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return success
  return 0;
}

//get hardware version
static int hkp_drv_file_ioctl_hw_ver( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_hw_ver * up_hw_ver )
{
  unsigned char buffer[9];
  int ret_val;
  struct t_hkp_drv_hw_ver hw_ver;

  //test pointer
  if( !access_ok( VERIFY_WRITE, up_hw_ver, sizeof( struct t_hkp_drv_hw_ver ) ) )
    //pointer not okay
    return -EFAULT;

  //ask hardware for its version
  ret_val = hkp_drv_send_msg_recv_msg( p_dev, 1, "\xFF",
                                              9, "\xFF\x00\x00\x00\x00\x00\x00\x00\x00",
                                                 "\xFF\x00\x00\x00\x00\x00\x00\x00\x00",
                                              buffer );

  //fill local structure
  hw_ver.variant = buffer[1];
  hw_ver.major = buffer[2];
  hw_ver.minor = buffer[3];
  hw_ver.revision = buffer[4];
  hw_ver.year = (unsigned short)buffer[5] << 8 | (unsigned short)buffer[6];
  hw_ver.month = buffer[7];
  hw_ver.day = buffer[8];

  //copy structure to user-space
  copy_to_user( up_hw_ver, &hw_ver, sizeof( struct t_hkp_drv_hw_ver ) );

  //debug message
  hkp_drv_debugf( "ioctl \"hw_ver\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return error code
  return ret_val;
}

//get identification
static int hkp_drv_file_ioctl_get_id( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_id * up_id )
{
  unsigned char buffer[17];
  int ret_val;
  struct t_hkp_drv_id id;

  //test pointer
  if( !access_ok( VERIFY_WRITE, up_id, sizeof( struct t_hkp_drv_id ) ) )
    //pointer not okay
    return -EFAULT;

  //ask hardware for its identification
  ret_val = hkp_drv_send_msg_recv_msg( p_dev, 1, "\xF0",
                                              17, "\xF0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
                                                  "\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
                                              buffer );

  //fill local structure
  memcpy( id.id, buffer + 1, sizeof( id.id ) );

  //copy structure to user-space
  copy_to_user( up_id, &id, sizeof( struct t_hkp_drv_id ) );

  //debug message
  hkp_drv_debugf( "ioctl \"get_id\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return error code
  return ret_val;
}

//set identification
static int hkp_drv_file_ioctl_set_id( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_id * up_id )
{
  unsigned char buffer[17];
  int ret_val;
  struct t_hkp_drv_id id;

  //test pointer
  if( !access_ok( VERIFY_READ, up_id, sizeof( struct t_hkp_drv_id ) ) )
    //pointer not okay
    return -EFAULT;

  //copy structure from user space
  copy_from_user( &id, up_id, sizeof( struct t_hkp_drv_id ) );

  //check if setting id is still possible and disable setting id
  //lock device
  down( &p_dev->lock );
  //device was unplugged
  if( p_dev->p_usb_dev == NULL )
  {
    //unlock device
    up( &p_dev->lock );
    //no such device there
    return -ENODEV;
  }
  //check if setting id is still possible and disable setting id
  if( ! xchg( &p_dev->set_id_possible, 0 ) )
  {
    //unlock device
    up( &p_dev->lock );
    //deny setting id
    return -EPERM;
  }
  //unlock device
  up( &p_dev->lock );

  //ask hardware to set identification
  buffer[0] = 0xF1;
  memcpy( buffer + 1, id.id, sizeof( buffer ) - 1 );
  ret_val = hkp_drv_send_msg_recv_msg( p_dev, 17, buffer, 2, "\xF1\x00", "\xFF\x00", buffer );
  if( ret_val == 0 ) //communication succeded
    if( buffer[1] != 0x00 ) //hardware reported error
      ret_val = -EIO; //report error to caller

  //debug message
  hkp_drv_debugf( "ioctl \"set_id\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return error code
  return ret_val;
}

//own (or free) ports
//tries to own or free some ports and returns owned ports
static int hkp_drv_file_ioctl_own( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_own * up_own )
{
  struct t_hkp_drv_own own;
  int i;

  //test pointer
  if( !access_ok( VERIFY_WRITE, up_own, sizeof( struct t_hkp_drv_own ) ) ) //WRITE implies READ
    //pointer not okay
    return -EFAULT;

  //copy structure from user-space
  copy_from_user( &own, up_own, sizeof( struct t_hkp_drv_own ) );

  //lock device
  down( &p_dev->lock );
  //device was unplugged
  if( p_dev->p_usb_dev == NULL )
  {
    //unlock device
    up( &p_dev->lock );
    //no such device there
    return -ENODEV;
  }

  //try to own or free requested ports
  for( i = 0; i < 2; i++ ) //output ports
    if( own.out[i] > 0 && !(p_dev->used_ports & hkp_drv_own_flags_out[i]) )
      p_dev->used_ports |= hkp_drv_own_flags_out[i],
      p_file_priv->owned_ports |= hkp_drv_own_flags_out[i];
    else if( own.out[i] < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_out[i]) )
      p_dev->used_ports &= ~hkp_drv_own_flags_out[i],
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_out[i];
  for( i = 0; i < 2; i++ ) //input ports
    if( own.in[i] > 0 && !(p_dev->used_ports & hkp_drv_own_flags_in[i]) )
      p_dev->used_ports |= hkp_drv_own_flags_in[i],
      p_file_priv->owned_ports |= hkp_drv_own_flags_in[i];
    else if( own.in[i] < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_in[i]) )
      p_dev->used_ports &= ~hkp_drv_own_flags_in[i],
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_in[i];
  for( i = 0; i < 2; i++ ) //PWM outputs
    if( own.pwm[i] > 0 && !(p_dev->used_ports & hkp_drv_own_flags_pwm[i]) )
      p_dev->used_ports |= hkp_drv_own_flags_pwm[i],
      p_file_priv->owned_ports |= hkp_drv_own_flags_pwm[i];
    else if( own.pwm[i] < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_pwm[i]) )
      p_dev->used_ports &= ~hkp_drv_own_flags_pwm[i],
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_pwm[i];
  for( i = 0; i < 5; i++ ) //analog inputs
    if( own.analog[i] > 0 && !(p_dev->used_ports & hkp_drv_own_flags_analog[i]) )
      p_dev->used_ports |= hkp_drv_own_flags_analog[i],
      p_file_priv->owned_ports |= hkp_drv_own_flags_analog[i];
    else if( own.analog[i] < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_analog[i]) )
      p_dev->used_ports &= ~hkp_drv_own_flags_analog[i],
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_analog[i];
  if( own.ser > 0 && !(p_dev->used_ports & hkp_drv_own_flags_ser) ) //serial port
      p_dev->used_ports |= hkp_drv_own_flags_ser,
      p_file_priv->owned_ports |= hkp_drv_own_flags_ser;
  else if( own.ser < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_ser) )
      p_dev->used_ports &= ~hkp_drv_own_flags_ser,
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_ser;
  if( own.i2c > 0 && !(p_dev->used_ports & hkp_drv_own_flags_i2c) ) //i2c
      p_dev->used_ports |= hkp_drv_own_flags_i2c,
      p_file_priv->owned_ports |= hkp_drv_own_flags_i2c;
  else if( own.i2c < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_i2c) )
      p_dev->used_ports &= ~hkp_drv_own_flags_i2c,
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_i2c;
  if( own.pic > 0 && !(p_dev->used_ports & hkp_drv_own_flags_pic) ) //pic
      p_dev->used_ports |= hkp_drv_own_flags_pic,
      p_file_priv->owned_ports |= hkp_drv_own_flags_pic;
  else if( own.pic < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_pic) )
      p_dev->used_ports &= ~hkp_drv_own_flags_pic,
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_pic;
  if( own.jtag > 0 && !(p_dev->used_ports & hkp_drv_own_flags_jtag) ) //jtag
      p_dev->used_ports |= hkp_drv_own_flags_jtag,
      p_file_priv->owned_ports |= hkp_drv_own_flags_jtag;
  else if( own.jtag < 0 && (p_file_priv->owned_ports & hkp_drv_own_flags_jtag) )
      p_dev->used_ports &= ~hkp_drv_own_flags_jtag,
      p_file_priv->owned_ports &= ~hkp_drv_own_flags_jtag;

  //return owned ports
  for( i = 0; i < 2; i++ ) //output ports
    own.out[i] = (p_file_priv->owned_ports & hkp_drv_own_flags_out[i]) ? 1 : -1;
  for( i = 0; i < 2; i++ ) //input ports
    own.in[i] = (p_file_priv->owned_ports & hkp_drv_own_flags_in[i]) ? 1 : -1;
  for( i = 0; i < 2; i++ ) //pwm outputs
    own.pwm[i] = (p_file_priv->owned_ports & hkp_drv_own_flags_pwm[i]) ? 1 : -1;
  for( i = 0; i < 5; i++ ) //analog inputs
    own.analog[i] = (p_file_priv->owned_ports & hkp_drv_own_flags_analog[i]) ? 1 : -1;
  own.ser = (p_file_priv->owned_ports & hkp_drv_own_flags_ser) ? 1 : -1; //serial port
  own.i2c = (p_file_priv->owned_ports & hkp_drv_own_flags_i2c) ? 1 : -1; //i2c
  own.pic = (p_file_priv->owned_ports & hkp_drv_own_flags_pic) ? 1 : -1; //pic
  own.jtag = (p_file_priv->owned_ports & hkp_drv_own_flags_jtag) ? 1 : -1; //jtag

  //unlock device
  up( &p_dev->lock );

  //copy structure to user-space
  copy_to_user( up_own, &own, sizeof( struct t_hkp_drv_own ) );

  //debug message
  hkp_drv_debugf( "ioctl \"own\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return success
  return 0;
}

//execute an action
static int hkp_drv_file_ioctl_act( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_act * up_act )
{
  struct t_hkp_drv_act act;
  int ret_val;

  //test pointer
  if( !access_ok( VERIFY_WRITE, up_act, sizeof( struct t_hkp_drv_act ) ) ) //WRITE implies READ
    //pointer not okay
    return -EFAULT;

  //copy structure from user-space
  copy_from_user( &act, up_act, sizeof( struct t_hkp_drv_act ) );

  //execute action
  ret_val = hkp_drv_acts_exec( p_file_priv, p_dev, 1, &act );

  //copy structure to user-space
  copy_to_user( up_act, &act, sizeof( struct t_hkp_drv_act ) );

  //debug message
  hkp_drv_debugf( "ioctl \"act\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return error code of action processing
  return ret_val;
}

//execute multiple actions
static int hkp_drv_file_ioctl_acts( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_acts * up_acts )
{
  struct t_hkp_drv_acts acts;
  int acts_size, ret_val;
  struct t_hkp_drv_act * p_acts;

  //test pointer
  if( !access_ok( VERIFY_READ, up_acts, sizeof( struct t_hkp_drv_acts ) ) )
    //pointer not okay
    return -EFAULT;

  //copy structure from user-space
  copy_from_user( &acts, up_acts, sizeof( struct t_hkp_drv_acts ) );

  //action count is zero
  if( acts.cnt == 0 )
    return -EINVAL;

  //get size of actions
  acts_size = acts.cnt * sizeof( struct t_hkp_drv_act );

  //test pointer
  if( !access_ok( VERIFY_WRITE, acts.p_acts, acts_size ) ) //WRITE implies READ
    //pointer not okay
    return -EFAULT;

  //allocate kernel-space buffer for actions
  p_acts = (struct t_hkp_drv_act *)kmalloc( acts_size, GFP_KERNEL );
  if( p_acts == NULL )
    return -ENOMEM;

  //copy structure from user-space
  copy_from_user( p_acts, acts.p_acts, acts_size );

  //execute actions
  ret_val = hkp_drv_acts_exec( p_file_priv, p_dev, acts.cnt, p_acts );

  //copy structure to user-space
  copy_to_user( acts.p_acts, p_acts, acts_size );

  //free kernel-space buffer for actions
  kfree( p_acts );

  //debug message
  hkp_drv_debugf( "ioctl \"acts\" (act_cnt=%d) on file %p (%d, %d)", acts.cnt, p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return error code of action processing
  return ret_val;
}

//set baudrate of serial port
static int hkp_drv_file_ioctl_ser_baud( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_ser_baud * up_ser_baud )
{
  unsigned char buffer[3];
  int ret_val;
  struct t_hkp_drv_ser_baud ser_baud;

  //test pointer
  if( !access_ok( VERIFY_READ, up_ser_baud, sizeof( struct t_hkp_drv_ser_baud ) ) )
    //pointer not okay
    return -EFAULT;

  //copy structure from user space
  copy_from_user( &ser_baud, up_ser_baud, sizeof( struct t_hkp_drv_ser_baud ) );

  //check if serial port is onwed
  if( hkp_drv_own_flags_ser & ~p_file_priv->owned_ports )
    return -EPERM;

  //set flag to clear send buffer
  p_dev->ser_send_clr = 1;
  
  //set flag for execution of hkp_drv_bulk_out_try for this device
  //(use test_and_x here to be smp safe)
  test_and_set_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[p_dev->dev_no_sub_minor] );
  //schedule task for things that can not be done in interrupt
  schedule_work( &hkp_drv_work );

  //set baudrate
  buffer[0] = 0x20;
  buffer[1] = (unsigned char)(ser_baud.bit_time >> 8);
  buffer[2] = (unsigned char)(ser_baud.bit_time & 0xFF);
  ret_val = hkp_drv_send_msg( p_dev, 3, buffer );
  
  //clear receive buffer
  p_dev->ser_recv_start = p_dev->ser_recv_end;

  //debug message
  hkp_drv_debugf( "ioctl \"ser_baud\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return error code
  return ret_val;
}

//send data on serial port
static int hkp_drv_file_ioctl_ser_send( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_ser_send * up_ser_send )
{
  struct t_hkp_drv_ser_send ser_send;
  unsigned short new_end;
  int len, i;

  //test pointer
  if( !access_ok( VERIFY_READ, up_ser_send, sizeof( struct t_hkp_drv_ser_send ) ) )
    //pointer not okay
    return -EFAULT;

  //copy structure from user space
  copy_from_user( &ser_send, up_ser_send, sizeof( struct t_hkp_drv_ser_send ) );

  //check if serial port is onwed
  if( hkp_drv_own_flags_ser & ~p_file_priv->owned_ports )
    return -EPERM;

  //check for space in send buffer
  len = min( (int)ser_send.len, (int)count( ser_send.data ) );
  new_end = p_dev->ser_send_end; 
  for( i = 0; i < len; i++ ) //check for len free bytes
  {
    new_end++; //advance new end pointer
    if( new_end >= count( p_dev->ser_send_buf ) )
      new_end = 0;
    if( new_end == p_dev->ser_send_start ) //buffer full
      return -EAGAIN;
  }

  //put bytes into buffer
  for( i = 0; i < len; i++ )
  {
    new_end = p_dev->ser_send_end; //get end pointer
    p_dev->ser_send_buf[new_end] = ser_send.data[i]; //put byte into buffer
    new_end++; //advance new end pointer
    if( new_end >= count( p_dev->ser_send_buf ) )
      new_end = 0;
    p_dev->ser_send_end = new_end; //atomically update end pointer
  }

  //set flag for execution of hkp_drv_bulk_out_try for this device
  //(use test_and_x here to be smp safe)
  test_and_set_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[p_dev->dev_no_sub_minor] );
  //schedule task for things that can not be done in interrupt
  schedule_work( &hkp_drv_work );
  
  //debug message
  hkp_drv_debugf( "ioctl \"ser_send\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return success
  return 0;
}

//receive data from serial port
static int hkp_drv_file_ioctl_ser_recv( struct file * p_file, struct t_hkp_drv_file_priv * p_file_priv, struct t_hkp_drv_dev * p_dev, struct t_hkp_drv_ser_recv * up_ser_recv )
{
  struct t_hkp_drv_ser_recv ser_recv;
  unsigned short new_start, value;

  //test pointer
  if( !access_ok( VERIFY_WRITE, up_ser_recv, sizeof( struct t_hkp_drv_ser_recv ) ) )
    //pointer not okay
    return -EFAULT;

  //check if serial port is onwed
  if( hkp_drv_own_flags_ser & ~p_file_priv->owned_ports )
    return -EPERM;

  //initialize local structure
  ser_recv.err_frame = 0; //no error
  ser_recv.err_ov_hw = 0;
  ser_recv.err_ov_fw = 0;
  ser_recv.err_ov_drv = 0;
  ser_recv.len = 0; //no data

  //fill local structure
  while( ser_recv.len < count( ser_recv.data ) )
  {
    //receive buffer is empty
    if( p_dev->ser_recv_start == p_dev->ser_recv_end )
      break;

    //get next entry from receive buffer
    new_start = p_dev->ser_recv_start; //get start pointer
    value = p_dev->ser_recv_buf[new_start]; //get the entry
    if( value >= 0x100 && ser_recv.len > 0 ) //not a data byte (i.e. some error) and already some data in structure
      break; //stop before this entry
    new_start++; //advance new start pointer
    if( new_start >= count( p_dev->ser_recv_buf ) )
      new_start = 0;
    p_dev->ser_recv_start = new_start; //atomically update start pointer

    //data byte: put it into strucutre
    if( value < 0x100 )
      ser_recv.data[ser_recv.len++] = (unsigned char)(value & 0xFF);
    //some error: put it into strucutre
    else
    {
      if( value >= 0x100 && value <= 0x1FF )
      {
        ser_recv.err_frame = value & 0x01 ? 1 : 0;
        ser_recv.err_ov_hw = value & 0x02 ? 1 : 0;
        ser_recv.err_ov_fw = value & 0x04 ? 1 : 0;
      }
      else
        ser_recv.err_ov_drv = 1;
      break; //do not add additional data bytes
    }
  }

  //copy structure to user-space
  copy_to_user( up_ser_recv, &ser_recv, sizeof( struct t_hkp_drv_ser_recv ) );

  //debug message
  hkp_drv_debugf( "ioctl \"ser_recv\" on file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //return success
  return 0;
}

//an userspace process is doing an ioctl on one of the devices
static int hkp_drv_file_ioctl( struct inode * p_inode, struct file * p_file, unsigned int ioctl_no, unsigned long ioctl_param )
{
  struct t_hkp_drv_file_priv * p_file_priv;
  struct t_hkp_drv_dev * p_dev;

  //get private file
  p_file_priv = (struct t_hkp_drv_file_priv *)p_file->private_data;
  //get private device
  p_dev = p_file_priv->p_dev;
  //no pointer to device
  if( p_dev == NULL )
    //so there is no device
    return -ENODEV;

  //choose ioctl
  switch( ioctl_no )
  {
    //get version of driver
    case hkp_drv_ioctl_no_ver:
      return hkp_drv_file_ioctl_ver( p_file, p_file_priv, p_dev, (struct t_hkp_drv_ver *)ioctl_param );
    //get hardware version
    case hkp_drv_ioctl_no_hw_ver:
      return hkp_drv_file_ioctl_hw_ver( p_file, p_file_priv, p_dev, (struct t_hkp_drv_hw_ver *)ioctl_param );
    //get identification
    case hkp_drv_ioctl_no_get_id:
      return hkp_drv_file_ioctl_get_id( p_file, p_file_priv, p_dev, (struct t_hkp_drv_id *)ioctl_param );
    //set identification
    case hkp_drv_ioctl_no_set_id:
      return hkp_drv_file_ioctl_set_id( p_file, p_file_priv, p_dev, (struct t_hkp_drv_id *)ioctl_param );
    //own (or free) ports
    case hkp_drv_ioctl_no_own:
      return hkp_drv_file_ioctl_own( p_file, p_file_priv, p_dev, (struct t_hkp_drv_own *)ioctl_param );
    //execute an action
    case hkp_drv_ioctl_no_act:
      return hkp_drv_file_ioctl_act( p_file, p_file_priv, p_dev, (struct t_hkp_drv_act *)ioctl_param );
    //execute multiple actions
    case hkp_drv_ioctl_no_acts:
      return hkp_drv_file_ioctl_acts( p_file, p_file_priv, p_dev, (struct t_hkp_drv_acts *)ioctl_param );
    //set baudrate of serial port
    case hkp_drv_ioctl_no_ser_baud:
      return hkp_drv_file_ioctl_ser_baud( p_file, p_file_priv, p_dev, (struct t_hkp_drv_ser_baud *)ioctl_param );
    //send data on serial port
    case hkp_drv_ioctl_no_ser_send:
      return hkp_drv_file_ioctl_ser_send( p_file, p_file_priv, p_dev, (struct t_hkp_drv_ser_send *)ioctl_param );
    //receive data from serial port
    case hkp_drv_ioctl_no_ser_recv:
      return hkp_drv_file_ioctl_ser_recv( p_file, p_file_priv, p_dev, (struct t_hkp_drv_ser_recv *)ioctl_param );
  }

  //unknown ioctl
  hkp_drv_debugf( "unknown ioctl %d on file %p (%d, %d)", ioctl_no, p_file, USB_MAJOR, p_dev->dev_no_minor );
  return -EINVAL;
}

//an userspace process is opening one of the devices
static int hkp_drv_file_open( struct inode * p_inode, struct file * p_file )
{
  struct t_hkp_drv_file_priv * p_file_priv;
  struct t_hkp_drv_dev * p_dev;
  int sub_minor;
  
  //get subminor device number
  sub_minor = MINOR( p_inode->i_rdev ) - hkp_drv_minor_base;
  if( sub_minor < 0  || sub_minor >= hkp_drv_minor_range )
    //device not one of our devices
    return -ENODEV;

  //lock device table
  down( &hkp_drv_dev_table_lock );
  //get device structure for this minor
  p_dev = hkp_drv_dev_table[sub_minor];
  //device not attached
  if( p_dev == NULL )
  {
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //device not available
    return -ENODEV;
  }

  //lock device
  down( &p_dev->lock );
  //unlock device table
  up( &hkp_drv_dev_table_lock );
  //device was unplugged
  if( p_dev->p_usb_dev == NULL )
  {
    //unlock device
    up( &p_dev->lock );
    //no such device there
    return -ENODEV;
  }
  
  //create private file data
  p_file_priv = (struct t_hkp_drv_file_priv *)kmalloc( sizeof( struct t_hkp_drv_file_priv ), GFP_KERNEL );
  if( p_file_priv == NULL )
  {
    //unlock device
    up( &p_dev->lock );
    //no memory available
    return -ENOMEM;
  }

  //initialize private file data
  //(p_dev and p_next_file_priv are initialized a few lines down)
  //no string to return on read allocated yet
  p_file_priv->p_read_str = NULL;
  p_file_priv->read_str_len = -1;
  //no ports owned yet
  p_file_priv->owned_ports = 0;
 
  //increment our usage count
  p_dev->open_cnt++;
  //disable setting id if second user
  if( p_dev->open_cnt > 1 )
    p_dev->set_id_possible = 0;

  //save pointer to private file data in file
  p_file->private_data = (void *)p_file_priv;
  //save pointer to device in the file's private structure
  p_file_priv->p_dev = p_dev;
  //put private file data into list in device structure
  //the list stays consistent all the time
  //(reading in interrupt is allowed when using only p_next or only p_prev)
  p_file_priv->p_prev = p_dev->file_priv_list.p_prev;
  p_file_priv->p_next = &p_dev->file_priv_list;
  p_file_priv->p_next->p_prev = p_file_priv;
  p_file_priv->p_prev->p_next = p_file_priv;

  //debug message
  hkp_drv_debugf( "opened file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //unlock device
  up( &p_dev->lock );

  //return success
  return 0;
}

//an userspace process is closing one of the devices
static int hkp_drv_file_release( struct inode * p_inode, struct file * p_file )
{
  struct t_hkp_drv_file_priv * p_file_priv;
  struct t_hkp_drv_dev * p_dev;

  //get private file
  p_file_priv = (struct t_hkp_drv_file_priv *)p_file->private_data;
  //get private device
  p_dev = p_file_priv->p_dev;
  //no pointer to device
  if( p_dev == NULL )
    //so there is no device
    return -ENODEV;

  //lock device table
  down( &hkp_drv_dev_table_lock );

  //lock device
  down( &p_dev->lock );

  //remove private file data from list in device structure
  //the list stays consistent all the time
  //(reading in interrupt is allowed when using only p_next or only p_prev)
  p_file_priv->p_next->p_prev = p_file_priv->p_prev;
  p_file_priv->p_prev->p_next = p_file_priv->p_next;

  //free string to return on read
  if( p_file_priv->p_read_str != NULL )
    kfree( p_file_priv->p_read_str );

  //free owned ports
  p_dev->used_ports &= ~p_file_priv->owned_ports;

  //free private file data
  kfree( p_file_priv );

  //check if device is open
  if( p_dev->open_cnt <= 0 )
  {
    //unlock device
    up( &p_dev->lock );
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //there is no such device
    return -ENODEV;
  }

  //debug message
  hkp_drv_debugf( "closed file %p (%d, %d)", p_file, USB_MAJOR, p_dev->dev_no_minor );

  //decrement usage count
  p_dev->open_cnt--;
  //disable setting id if second user
  p_dev->set_id_possible = 0;

  //device was unplugged
  if( p_dev->p_usb_dev == NULL )
  {
    //device is opened by someone else
    if( p_dev->open_cnt > 0 )
    {
      //unlock the device
      up( &p_dev->lock );
    }
    //device is not opened by anyone else
    else
    {
      //unlock the device
      up( &p_dev->lock );
      //free the device (also frees buffers and URBs)
      //(and remove it from the device table)
      hkp_drv_dev_free( p_dev );
    }
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //success
    return 0;
  }

  //unlock device
  up( &p_dev->lock );
  //unlock device table
  up( &hkp_drv_dev_table_lock );

  //return success
  return 0;
}

//structure for supported file operations
static struct file_operations hkp_drv_file_operations =
{
  owner:   THIS_MODULE, //because of this, we don't need the MOD_INC/DEC_USE_COUNT macros
  llseek:  NULL,
  read:    hkp_drv_file_read,
  write:   hkp_drv_file_write,
  readdir: NULL,
  poll:    NULL,
  ioctl:   hkp_drv_file_ioctl,
  mmap:    NULL,
  open:    hkp_drv_file_open,
  flush:   NULL,
  release: hkp_drv_file_release,
};



//*** USB device operations

//usb class driver info
static struct usb_class_driver hkp_drv_usb_class =
{
  .name =         "usb/hkp_drv%d",
  .fops =         &hkp_drv_file_operations,
#if LINUX_VERSION_CODE < 0x2060F //kernel-version 2.6.15
  .mode =         S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
#endif
  .minor_base =   hkp_drv_minor_base,
};

//device is attached to USB
static int hkp_drv_probe( struct usb_interface * p_interface, const struct usb_device_id * p_usb_dev_id )
{
  struct usb_device * p_usb_dev;
  unsigned char sub_minor;
  struct t_hkp_drv_dev * p_dev;
  struct usb_host_interface * p_interface_descriptor;
  int i, endpoints_ok, retval;

  //get pointer to usb device
  p_usb_dev = interface_to_usbdev( p_interface );

  //check if vendor id and product id match
  if( p_usb_dev->descriptor.idVendor != hkp_drv_id_vendor || p_usb_dev->descriptor.idProduct != hkp_drv_id_product )
    //device can't be handled
    //(this is not an error, it's just not our device)
    return -ENODEV;

  //now, we are sure, that the device is a hkp_drv-device

  //lock device table
  down( &hkp_drv_dev_table_lock );
  //select a free subminor number
  for( sub_minor = 0; sub_minor < hkp_drv_minor_range; sub_minor++ )
    if( hkp_drv_dev_table[sub_minor] == NULL )
      break;
  //no free subminor found
  if( sub_minor >= hkp_drv_minor_range )
  {
    //print error message
    hkp_drv_error( "too much devices" );
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //device can't be handled
    return -ENODEV;
  }

  //allocate buffer for private data
  p_dev = (struct t_hkp_drv_dev *)kmalloc( sizeof( struct t_hkp_drv_dev ), GFP_KERNEL );
  //error
  if( p_dev == NULL )
  {
    //print error message
    hkp_drv_error( "could not allocate buffer for private data" );
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //abort function
    return -ENOMEM;
  }

  //put device into device table
  hkp_drv_dev_table[sub_minor] = p_dev;

  //initialize lock
  init_MUTEX( &p_dev->lock );
  //save usb device data
  p_dev->p_usb_dev = p_usb_dev;
  p_dev->p_usb_dev_id = p_usb_dev_id;
  //save minor device number
  p_dev->dev_no_sub_minor = sub_minor;
  p_dev->dev_no_minor = hkp_drv_minor_base + p_dev->dev_no_sub_minor;
  //no buffers yet
  for( i = 0; i < 3; i++ )
  {
    p_dev->p_bulk_out_buffer[i] = NULL;
    p_dev->p_bulk_in_buffer[i] = NULL;
  }
  //no URBs yet
  for( i = 0; i < 3; i++ )
  {
    p_dev->p_bulk_out_urb[i] = NULL;
    p_dev->p_bulk_in_urb[i] = NULL;
  }
  //URBs not submitted/linked yet, no data received yet
  for( i = 0; i < 3; i++ )
  {
    p_dev->bulk_out_urb_linked[i] = 0;
    p_dev->bulk_in_urb_linked[i] = 0;
    p_dev->bulk_in_actual_length[i] = -1;
  }
  //no bulk input error yet
  test_and_clear_bit( 0, &p_dev->bulk_in_err );
  //use first bulk out URB for first packet
  p_dev->next_bulk_out_index = 0;
  //reset sequence numbers
  p_dev->seq_no_send = 0;
  p_dev->seq_no_recv = 0;
  //device is not opened yet
  p_dev->open_cnt = 0;
  //no files have opened this device
  p_dev->file_priv_list.p_dev = NULL;
  p_dev->file_priv_list.p_next = &p_dev->file_priv_list;
  p_dev->file_priv_list.p_prev = &p_dev->file_priv_list;
  //no output steps yet
  p_dev->out_step_list.p_next = &p_dev->out_step_list;
  p_dev->out_step_list.p_prev = &p_dev->out_step_list;
  p_dev->out_step_list.msg_len = -1;
  p_dev->out_step_list.p_msg = NULL;
  //no input steps yet
  p_dev->in_step_list.p_next = &p_dev->in_step_list;
  p_dev->in_step_list.p_prev = &p_dev->in_step_list;
  p_dev->in_step_list.msg_len = -1;
  p_dev->in_step_list.p_filter = NULL;
  p_dev->in_step_list.p_mask = NULL;
  p_dev->in_step_list.p_msg = NULL;
  //no ports used yet
  p_dev->used_ports = 0;
  //setting id is still possible
  p_dev->set_id_possible = 1;
  //empty buffers for serial port
  p_dev->ser_send_start = 0;
  p_dev->ser_send_end = 0;
  p_dev->ser_send_clr = 0; //flag to clear send buffer not set
  p_dev->ser_recv_start = 0;
  p_dev->ser_recv_end = 0;

  //get interface descriptor
  p_interface_descriptor = &p_interface->altsetting[0];

  //check endpoints: we want: interrupt out, interrupt in, bulk out, bulk in
  endpoints_ok = 0;
  if( p_interface_descriptor->desc.bNumEndpoints == 6 ) //exactly 6 endpoints
  {
    endpoints_ok = 1;
    //3 times
    for( i = 0; i < 3; i++ )
    {
      //bulk out
      if( (p_interface_descriptor->endpoint[i<<1].desc.bmAttributes & 0x03) == 0x02 //bulk
        && (p_interface_descriptor->endpoint[i<<1].desc.bEndpointAddress & 0x80) == 0x00 ) //out
      {
        //get bulk out parameters
        p_dev->bulk_out_endpoint_addr[i] = p_interface_descriptor->endpoint[i<<1].desc.bEndpointAddress & 0x7F;
        p_dev->bulk_out_max_packet_size[i] = p_interface_descriptor->endpoint[i<<1].desc.wMaxPacketSize;
      }
      else
      {
        //endpoints are not okay
        endpoints_ok = 0;
      }
      //bulk in
      if( (p_interface_descriptor->endpoint[i<<1|1].desc.bmAttributes & 0x03) == 0x02 //bulk
        && (p_interface_descriptor->endpoint[i<<1|1].desc.bEndpointAddress & 0x80) != 0x00 ) //in
      {
        //get bulk in parameters
        p_dev->bulk_in_endpoint_addr[i] = p_interface_descriptor->endpoint[i<<1|1].desc.bEndpointAddress & 0x7F;
        p_dev->bulk_in_max_packet_size[i] = p_interface_descriptor->endpoint[i<<1|1].desc.wMaxPacketSize;
      }
      else
      {
        //endpoints are not okay
        endpoints_ok = 0;
      }
    }
  }

  //endpoints are not okay
  if( !endpoints_ok )
  {
    //print error message
    hkp_drv_error( "device has got unexpected endpoints" );
    //free device (also frees buffers and URBs)
    hkp_drv_dev_free( p_dev );
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //abort function
    return -ENODEV;
  }

  //allocate buffers
  for( i = 0; i < 3; i++ )
  {
    p_dev->p_bulk_out_buffer[i] = kmalloc( p_dev->bulk_out_max_packet_size[i], GFP_KERNEL );
    p_dev->p_bulk_in_buffer[i] = kmalloc( p_dev->bulk_in_max_packet_size[i], GFP_KERNEL );
  }
  if( p_dev->p_bulk_out_buffer[0] == NULL || p_dev->p_bulk_in_buffer[0] == NULL
   || p_dev->p_bulk_out_buffer[1] == NULL || p_dev->p_bulk_in_buffer[1] == NULL
   || p_dev->p_bulk_out_buffer[2] == NULL || p_dev->p_bulk_in_buffer[2] == NULL )
  {
    //print error message
    hkp_drv_error( "could not allocate buffers" );
    //free device (also frees buffers and URBs)
    hkp_drv_dev_free( p_dev );
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //abort function
    return -ENOMEM;
  }

  //allocate URBs
  for( i = 0; i < 3; i++ )
  {
    p_dev->p_bulk_out_urb[i] = usb_alloc_urb( 0, GFP_KERNEL );
    p_dev->p_bulk_in_urb[i] = usb_alloc_urb( 0, GFP_KERNEL );
  }
  if( p_dev->p_bulk_out_urb[0] == NULL || p_dev->p_bulk_in_urb[0] == NULL
   || p_dev->p_bulk_out_urb[1] == NULL || p_dev->p_bulk_in_urb[1] == NULL
   || p_dev->p_bulk_out_urb[2] == NULL || p_dev->p_bulk_in_urb[2] == NULL )
  {
    //print error message
    hkp_drv_error( "could not allocate URBs" );
    //free device (also frees buffers and URBs)
    hkp_drv_dev_free( p_dev );
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //abort function
    return -ENOMEM;
  }

  //start the 3 bulk inputs
  for( i = 0; i < 3; i++ )
  {
    //set up data in bulk in URB
    usb_fill_bulk_urb( p_dev->p_bulk_in_urb[i], p_usb_dev, usb_rcvbulkpipe( p_usb_dev, p_dev->bulk_in_endpoint_addr[i] ),
        p_dev->p_bulk_in_buffer[i], p_dev->bulk_in_max_packet_size[i], hkp_drv_bulk_in_callback,
        p_dev );
    //bulk in URB will be linked/submitted soon
    //(do this here because callback could perhaps be called from usb_submit_urb)
    p_dev->bulk_in_urb_linked[i] = 1;
    //submit bulk in URB
    if( usb_submit_urb( p_dev->p_bulk_in_urb[i], GFP_KERNEL ) )
    {
      //bulk in URB is not linked/submitted
      p_dev->bulk_in_urb_linked[i] = 0;
      //print error message
      hkp_drv_errorf( "could not submit bulk in URB %d", i );
      //free device (also frees buffers and URBs)
      hkp_drv_dev_free( p_dev );
      //unlock device table
      up( &hkp_drv_dev_table_lock );
      //abort function
      return -ENOMEM;
    }
    //print info that bulk in was started
    hkp_drv_debugf( "bulk input %d started (max_len=%d)", i, p_dev->p_bulk_in_urb[i]->transfer_buffer_length );
  }

  //register the device
  usb_set_intfdata( p_interface, p_dev );
  retval = usb_register_dev( p_interface, &hkp_drv_usb_class );
  if( retval != 0 )
  {
    usb_set_intfdata( p_interface, NULL );
    //print error message
    hkp_drv_errorf( "could not register device: %d", retval );
    //free device (also frees buffers and URBs)
    hkp_drv_dev_free( p_dev );
    //unlock device table
    up( &hkp_drv_dev_table_lock );
    //abort function
    return retval;
  }
  
  //remember minor number
  p_dev->dev_no_minor = p_interface->minor;

  //print info message
  hkp_drv_infof( "device attached (major %d, minor %d)", USB_MAJOR, p_dev->dev_no_minor );

  //unlock device table
  up( &hkp_drv_dev_table_lock );

  //return success
  return 0;
}

//device is removed from USB
static void hkp_drv_disconnect( struct usb_interface * p_interface )
{
  struct t_hkp_drv_dev * p_dev;

  //get pointer to device
  p_dev = (struct t_hkp_drv_dev *)usb_get_intfdata( p_interface );

  //lock device table and device
  down( &hkp_drv_dev_table_lock );
  down( &p_dev->lock );

  //print info message
  hkp_drv_infof( "device disconnected (major %d, minor %d)", USB_MAJOR, p_dev->dev_no_minor );

  //unregister device
  usb_set_intfdata( p_interface, NULL );
  usb_deregister_dev( p_interface, &hkp_drv_usb_class );

  //device is opened by someone
  if( p_dev->open_cnt > 0 )
  {
    //remove pointer to usb device
    //(device is gone)
    p_dev->p_usb_dev = NULL;
    //unlock the device
    up( &p_dev->lock );
  }
  //device is not opened by anyone
  else
  {
    //unlock the device
    up( &p_dev->lock );
    //free the device (and remove it from the device table)
    hkp_drv_dev_free( p_dev );
  }

  //unlock device table
  up( &hkp_drv_dev_table_lock );
}



//*** module init and exit

//module is being loaded
static int __init hkp_drv_init( void )
{
  //local variables
  int i;
  
#ifdef DEBUG_MEM
  //initialize memory debug lock
  init_MUTEX( &hkp_drv_my_alloc_lock );
#endif
  
  //short message
  hkp_drv_info( hkp_drv_name" ("hkp_drv_ver")" );

  //initialize table with pointers to private device data of devices
  for( i = 0; i < hkp_drv_minor_range; i++ )
    hkp_drv_dev_table[i] = NULL;
  //initialize the lock for this table
  init_MUTEX( &hkp_drv_dev_table_lock );

  //reset boolean flags for tasks to do in next hkp_drv_task
  //(hkp_drv_task is task for things that cannot be done in interrupt)
  //(use test_and_x to be smp safe)
  for( i = 0; i < hkp_drv_minor_range; i++ )
  {
    test_and_clear_bit( hkp_drv_task_flag_bulk_out_try, &hkp_drv_task_flags[i] );
    test_and_clear_bit( hkp_drv_task_flag_bulk_in_proc, &hkp_drv_task_flags[i] );
  }

  //register this driver with the USB subsystem
  i = usb_register( &hkp_drv_usb_driver );
  //an error occured
  if( i < 0 )
  {
    //error-message
    hkp_drv_errorf( "usb_register failed with %d", i );
    //init failed
    return -1;
  }

  //init succeeded
  hkp_drv_info( "loaded..." );
  return 0;
}

//module is being unloaded
static void __exit hkp_drv_exit( void )
{
  //deregister driver with the USB subsystem
  usb_deregister( &hkp_drv_usb_driver );

  //wait until task is executed (task may be scheduled)
  flush_scheduled_work( );

  //exit done
  hkp_drv_info( "unloaded..." );
}



//module init and exit procedure
module_init( hkp_drv_init );
module_exit( hkp_drv_exit );

MODULE_AUTHOR( "stefan <stefan@schuermans.info>" );
MODULE_DESCRIPTION( hkp_drv_name );
MODULE_LICENSE( "GPL" );
